Add test data generator

This commit is contained in:
Anton Medvedev
2023-08-18 00:15:32 +02:00
parent 74796caae1
commit 3022c051ea
5 changed files with 656 additions and 0 deletions
+1
View File
@@ -35,6 +35,7 @@ type Env struct {
ArrayOfFoo []*Foo
MapOfFoo map[string]Foo
MapOfAny map[string]interface{}
MapIntAny map[int]string
FuncParam func(_ bool, _ int, _ string) bool
FuncParamAny func(_ interface{}) bool
FuncTooManyReturns func() (int, int, error)
+119
View File
@@ -0,0 +1,119 @@
package playground
import (
"fmt"
"time"
)
func ExampleData() interface{} {
profileJohn := UserProfile{
Birthday: time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC),
Biography: "A passionate writer about Go.",
Website: "https://johndoe.com",
}
profileJane := UserProfile{
Birthday: time.Date(1985, 2, 15, 0, 0, 0, 0, time.UTC),
Biography: "A web developer and writer.",
Website: "https://jane.com",
}
authorJohn := Author{
ID: 1,
FirstName: "John",
LastName: "Doe",
Email: "[email protected]",
Profile: profileJohn,
}
authorJane := Author{
ID: 2,
FirstName: "Jane",
LastName: "Smith",
Email: "[email protected]",
Profile: profileJane,
}
posts := []Post{
{
ID: 1,
Title: "Understanding Golang",
Content: "Go is an open-source programming language...",
PublishDate: time.Now().AddDate(0, -1, 0),
Author: authorJohn,
Comments: generateComments(3),
Tags: []string{"Go", "Programming"},
Likes: 100,
},
{
ID: 2,
Title: "Exploring Python",
Content: "Python is versatile...",
PublishDate: time.Now().AddDate(0, -2, 0),
Author: authorJane,
Comments: generateComments(4),
Tags: []string{"Python", "Development"},
Likes: 150,
},
{
ID: 3,
Title: "Web Development Basics",
Content: "The world of web development...",
PublishDate: time.Now().AddDate(0, -3, 0),
Author: authorJane,
Comments: generateComments(5),
Tags: []string{"Web", "HTML", "CSS"},
Likes: 125,
},
{
ID: 4,
Title: "Machine Learning in a Nutshell",
Content: "ML is revolutionizing industries...",
PublishDate: time.Now().AddDate(0, -5, 0),
Author: authorJohn,
Comments: generateComments(6),
Tags: []string{"ML", "AI"},
Likes: 200,
},
{
ID: 5,
Title: "JavaScript: The Good Parts",
Content: "JavaScript powers the web...",
PublishDate: time.Now().AddDate(0, -4, 0),
Author: authorJane,
Comments: generateComments(3),
Tags: []string{"JavaScript", "Web"},
Likes: 170,
},
}
blog := Blog{
Posts: make([]Post, len(posts)),
Authors: map[int]Author{authorJohn.ID: authorJohn, authorJane.ID: authorJane},
TotalViews: 10000,
TotalPosts: len(posts),
TotalLikes: 0,
}
for i, post := range posts {
blog.Posts[i] = post
blog.TotalLikes += post.Likes
}
return blog
}
func generateComments(count int) []Comment {
var comments []Comment
for i := 1; i <= count; i++ {
comment := Comment{
ID: i,
AuthorName: fmt.Sprintf("Commenter %d", i),
Content: fmt.Sprintf("This is comment %d!", i),
CommentDate: time.Now().AddDate(0, 0, -i),
Upvotes: i * 5,
}
comments = append(comments, comment)
}
return comments
}
+125
View File
@@ -10,6 +10,10 @@ type UserProfile struct {
Website string
}
func (u UserProfile) Age() int {
return time.Now().Year() - u.Birthday.Year()
}
type Author struct {
ID int
FirstName string
@@ -18,6 +22,14 @@ type Author struct {
Profile UserProfile
}
func (a Author) FullName() string {
return a.FirstName + " " + a.LastName
}
func (a Author) IsAdmin() bool {
return a.ID == 1
}
type Post struct {
ID int
Title string
@@ -29,6 +41,35 @@ type Post struct {
Likes int
}
func (p Post) Published() bool {
return !p.PublishDate.IsZero()
}
func (p Post) After(date time.Time) bool {
return p.PublishDate.After(date)
}
func (p Post) Before(date time.Time) bool {
return p.PublishDate.Before(date)
}
func (p Post) Compare(other Post) int {
if p.PublishDate.Before(other.PublishDate) {
return -1
} else if p.PublishDate.After(other.PublishDate) {
return 1
}
return 0
}
func (p Post) Equal(other Post) bool {
return p.Compare(other) == 0
}
func (p Post) IsZero() bool {
return p.ID == 0 && p.Title == "" && p.Content == "" && p.PublishDate.IsZero()
}
type Comment struct {
ID int
AuthorName string
@@ -37,6 +78,14 @@ type Comment struct {
Upvotes int
}
func (c Comment) Upvoted() bool {
return c.Upvotes > 0
}
func (c Comment) AuthorEmail() string {
return c.AuthorName + "@example.com"
}
type Blog struct {
Posts []Post
Authors map[int]Author
@@ -44,3 +93,79 @@ type Blog struct {
TotalPosts int
TotalLikes int
}
func (b Blog) RecentPosts() []Post {
var posts []Post
for _, post := range b.Posts {
if post.Published() {
posts = append(posts, post)
}
}
return posts
}
func (b Blog) PopularPosts() []Post {
var posts []Post
for _, post := range b.Posts {
if post.Likes > 150 {
posts = append(posts, post)
}
}
return posts
}
func (b Blog) TotalUpvotes() int {
var upvotes int
for _, post := range b.Posts {
for _, comment := range post.Comments {
upvotes += comment.Upvotes
}
}
return upvotes
}
func (b Blog) TotalComments() int {
var comments int
for _, post := range b.Posts {
comments += len(post.Comments)
}
return comments
}
func (Blog) Add(a, b float64) float64 {
return a + b
}
func (Blog) Sub(a, b float64) float64 {
return a - b
}
func (Blog) Title(post Post) string {
return post.Title
}
func (Blog) HasTag(post Post, tag string) bool {
for _, t := range post.Tags {
if t == tag {
return true
}
}
return false
}
func (Blog) IsAdmin(author Author) bool {
return author.IsAdmin()
}
func (Blog) IsZero(post Post) bool {
return post.IsZero()
}
func (Blog) WithID(posts []Post, id int) Post {
for _, post := range posts {
if post.ID == id {
return post
}
}
return Post{}
}
+358
View File
@@ -0,0 +1,358 @@
package main
import (
"fmt"
"math/rand"
"github.com/antonmedv/expr"
"github.com/antonmedv/expr/ast"
"github.com/antonmedv/expr/builtin"
"github.com/antonmedv/expr/test/playground"
)
func main() {
var code string
defer func() {
if r := recover(); r != nil {
fmt.Printf("==========================\n%s\n==========================\n", code)
panic(r)
}
}()
corpus := map[string]struct{}{}
for {
code = node(weightedRandomInt([]intWeight{
{3, 100},
{4, 40},
{5, 50},
{6, 30},
{7, 20},
{8, 10},
{9, 5},
{10, 5},
})).String()
program, err := expr.Compile(code, expr.Env(playground.Blog{}))
if err != nil {
continue
}
_, err = expr.Run(program, playground.ExampleData())
if err != nil {
continue
}
if _, ok := corpus[code]; ok {
continue
}
corpus[code] = struct{}{}
fmt.Println(code)
}
}
func node(depth int) ast.Node {
if depth <= 0 {
return weightedRandom([]fnWeight{
{nilNode, 1},
{floatNode, 1},
{integerNode, 1},
{stringNode, 1},
{booleanNode, 1},
{identifierNode, 10},
})(depth - 1)
}
return weightedRandom([]fnWeight{
{arrayNode, 1},
{mapNode, 1},
{identifierNode, 1000},
{memberNode, 1500},
{unaryNode, 100},
{binaryNode, 2000},
{callNode, 2000},
{builtinNode, 500},
{predicateNode, 1000},
{pointerNode, 500},
{sliceNode, 100},
{conditionalNode, 100},
})(depth - 1)
}
func nilNode(depth int) ast.Node {
return &ast.NilNode{}
}
func floatNode(depth int) ast.Node {
cases := []float64{
0.0,
0.5,
}
return &ast.FloatNode{
Value: cases[rand.Intn(len(cases))],
}
}
func integerNode(depth int) ast.Node {
return &ast.IntegerNode{
Value: rand.Intn(3),
}
}
func stringNode(depth int) ast.Node {
corpus := []string{
"Go", "JavaScript", " ",
}
return &ast.StringNode{
Value: corpus[rand.Intn(len(corpus))],
}
}
func booleanNode(depth int) ast.Node {
return &ast.BoolNode{
Value: maybe(),
}
}
func identifierNode(depth int) ast.Node {
cases := []string{
"Posts",
"Authors",
"TotalViews",
}
return &ast.IdentifierNode{
Value: cases[rand.Intn(len(cases))],
}
}
func memberNode(depth int) ast.Node {
cases := []string{
"Birthday",
"Biography",
"Website",
"ID",
"FirstName",
"LastName",
"Email",
"Profile",
"ID",
"Title",
"Content",
"PublishDate",
"Author",
"Comments",
"Tags",
"Likes",
"ID",
"AuthorName",
"Content",
"CommentDate",
"Upvotes",
}
return &ast.MemberNode{
Node: node(depth - 1),
Property: weightedRandom([]fnWeight{
{func(_ int) ast.Node { return &ast.StringNode{Value: cases[rand.Intn(len(cases))]} }, 5},
{node, 1},
})(depth - 1),
Optional: maybe(),
}
}
func unaryNode(depth int) ast.Node {
cases := []string{
"-",
"!",
"not",
}
return &ast.UnaryNode{
Operator: cases[rand.Intn(len(cases))],
Node: node(depth - 1),
}
}
func binaryNode(depth int) ast.Node {
cases := []string{
"or",
"||",
"and",
"&&",
"==",
"!=",
"<",
">",
">=",
"<=",
"in",
"matches",
"contains",
"startsWith",
"endsWith",
"..",
"+",
"-",
"*",
"/",
"%",
"**",
"^",
}
return &ast.BinaryNode{
Operator: cases[rand.Intn(len(cases))],
Left: node(depth - 1),
Right: node(depth - 1),
}
}
func methodNode(depth int) ast.Node {
cases := []string{
"Age",
"FullName",
"IsAdmin",
"Published",
"After",
"Before",
"Compare",
"Equal",
"IsZero",
"Upvoted",
"AuthorEmail",
}
return &ast.MemberNode{
Node: node(depth - 1),
Property: &ast.StringNode{Value: cases[rand.Intn(len(cases))]},
Optional: maybe(),
}
}
func funcNode(depth int) ast.Node {
cases := []string{
"RecentPosts",
"PopularPosts",
"TotalUpvotes",
"TotalComments",
"Add",
"Sub",
"Title",
"HasTag",
"IsAdmin",
"IsZero",
"WithID",
}
return &ast.IdentifierNode{
Value: cases[rand.Intn(len(cases))],
}
}
func callNode(depth int) ast.Node {
var args []ast.Node
max := weightedRandomInt([]intWeight{
{1, 100},
{2, 50},
{3, 25},
{4, 10},
{5, 5},
})
for i := 0; i < max; i++ {
args = append(args, node(depth-1))
}
return &ast.CallNode{
Callee: weightedRandom([]fnWeight{
{methodNode, 2},
{funcNode, 2},
})(depth - 1),
Arguments: args,
}
}
func builtinNode(depth int) ast.Node {
var args []ast.Node
max := weightedRandomInt([]intWeight{
{1, 100},
{2, 50},
{3, 25},
{4, 10},
{5, 5},
})
for i := 0; i < max; i++ {
args = append(args, node(depth-1))
}
return &ast.BuiltinNode{
Name: builtin.Names[rand.Intn(len(builtin.Names))],
Arguments: args,
}
}
func predicateNode(depth int) ast.Node {
cases := []string{
"all",
"none",
"any",
"one",
"filter",
"map",
"count",
}
return &ast.BuiltinNode{
Name: cases[rand.Intn(len(cases))],
Arguments: []ast.Node{node(depth - 1), node(depth - 1)},
}
}
func pointerNode(depth int) ast.Node {
return &ast.PointerNode{}
}
func arrayNode(depth int) ast.Node {
var items []ast.Node
max := weightedRandomInt([]intWeight{
{1, 100},
{2, 50},
{3, 25},
{4, 10},
{5, 5},
})
for i := 0; i < max; i++ {
items = append(items, node(depth-1))
}
return &ast.ArrayNode{
Nodes: items,
}
}
func mapNode(depth int) ast.Node {
var items []ast.Node
max := weightedRandomInt([]intWeight{
{1, 100},
{2, 50},
{3, 25},
{4, 10},
{5, 5},
})
for i := 0; i < max; i++ {
items = append(items, &ast.PairNode{
Key: stringNode(depth - 1),
Value: node(depth - 1),
})
}
return &ast.MapNode{
Pairs: items,
}
}
func sliceNode(depth int) ast.Node {
return &ast.SliceNode{
Node: node(depth - 1),
From: node(depth - 1),
To: node(depth - 1),
}
}
func conditionalNode(depth int) ast.Node {
return &ast.ConditionalNode{
Cond: node(depth - 1),
Exp1: node(depth - 1),
Exp2: node(depth - 1),
}
}
+53
View File
@@ -0,0 +1,53 @@
package main
import (
"math/rand"
"github.com/antonmedv/expr/ast"
)
func maybe() bool {
return rand.Intn(2) == 0
}
type fn func(int) ast.Node
type fnWeight struct {
value fn
weight int
}
func weightedRandom(cases []fnWeight) fn {
totalWeight := 0
for _, c := range cases {
totalWeight += c.weight
}
r := rand.Intn(totalWeight)
for _, c := range cases {
if r < c.weight {
return c.value
}
r -= c.weight
}
return cases[0].value
}
type intWeight struct {
value int
weight int
}
func weightedRandomInt(cases []intWeight) int {
totalWeight := 0
for _, c := range cases {
totalWeight += c.weight
}
r := rand.Intn(totalWeight)
for _, c := range cases {
if r < c.weight {
return c.value
}
r -= c.weight
}
return cases[0].value
}