common/testing/event_generator.go (374 lines of code) (raw):

// Copyright (c) 2019 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package testing import "math/rand" const ( emptyCandidateIndex = -1 defaultVersion = int64(100) ) var ( defaultBatchFunc = func(batch []Vertex, history []Vertex) bool { return len(batch) == 0 } ) type ( // HistoryEventVertex represents one type of history event HistoryEventVertex struct { name string isStrictOnNextVertex bool maxNextGeneration int dataFunc func(...interface{}) interface{} data interface{} } // HistoryEventModel is a graph represents relationships among history event types HistoryEventModel struct { edges []Edge } // EventGenerator is a history event generator // The event generator will generate next history event // based on the history event transition graph defined in the model EventGenerator struct { connections map[string][]Edge previousVertices []Vertex leafVertices []Vertex entryVertices []Vertex exitVertices map[string]bool randomEntryVertices []Vertex dice *rand.Rand seed int64 canDoBatch func([]Vertex, []Vertex) bool version int64 } // HistoryEventEdge is the directional edge of two history events HistoryEventEdge struct { startVertex Vertex endVertex Vertex condition func(...interface{}) bool action func() } ) // NewEventGenerator initials the event generator func NewEventGenerator( seed int64, ) Generator { return &EventGenerator{ connections: make(map[string][]Edge), previousVertices: make([]Vertex, 0), leafVertices: make([]Vertex, 0), entryVertices: make([]Vertex, 0), exitVertices: make(map[string]bool), randomEntryVertices: make([]Vertex, 0), dice: rand.New(rand.NewSource(seed)), seed: seed, canDoBatch: defaultBatchFunc, version: defaultVersion, } } // AddInitialEntryVertex adds the initial history event vertices // Generator will only start from one of the entry vertex func (g *EventGenerator) AddInitialEntryVertex( entry ...Vertex, ) { g.entryVertices = append( g.entryVertices, entry...) } // AddExitVertex adds the terminate history event vertex func (g *EventGenerator) AddExitVertex( exit ...Vertex, ) { for _, v := range exit { g.exitVertices[v.GetName()] = true } } // AddRandomEntryVertex adds the random history event vertex func (g *EventGenerator) AddRandomEntryVertex( exit ...Vertex, ) { g.randomEntryVertices = append(g.randomEntryVertices, exit...) } // AddModel adds a history event model func (g *EventGenerator) AddModel( model Model, ) { for _, e := range model.ListEdges() { if _, ok := g.connections[e.GetStartVertex().GetName()]; !ok { g.connections[e.GetStartVertex().GetName()] = make([]Edge, 0) } g.connections[e.GetStartVertex().GetName()] = append(g.connections[e.GetStartVertex().GetName()], e) } } // ListGeneratedVertices returns all the generated history events func (g EventGenerator) ListGeneratedVertices() []Vertex { return g.previousVertices } // HasNextVertex checks if there is accessible history event vertex func (g *EventGenerator) HasNextVertex() bool { for _, prev := range g.previousVertices { if _, ok := g.exitVertices[prev.GetName()]; ok { return false } } return len(g.leafVertices) > 0 || (len(g.previousVertices) == 0 && len(g.entryVertices) > 0) } // GetNextVertices generates a batch of history events happened in the same transaction func (g *EventGenerator) GetNextVertices() []Vertex { if !g.HasNextVertex() { panic("Generator reached to a terminate state.") } batch := make([]Vertex, 0) for g.HasNextVertex() && g.canDoBatch(batch, g.previousVertices) { res := g.generateNextEventBatch() g.updateContext(res) batch = append(batch, res...) } return batch } // Reset reset the generator to the initial state func (g *EventGenerator) Reset() { g.leafVertices = make([]Vertex, 0) g.previousVertices = make([]Vertex, 0) g.version = defaultVersion } // DeepCopy copy a new instance of generator func (g *EventGenerator) DeepCopy() Generator { return &EventGenerator{ connections: copyConnections(g.connections), previousVertices: copyVertex(g.previousVertices), leafVertices: copyVertex(g.leafVertices), entryVertices: copyVertex(g.entryVertices), exitVertices: copyExitVertices(g.exitVertices), randomEntryVertices: copyVertex(g.randomEntryVertices), dice: rand.New(rand.NewSource(g.seed)), seed: g.seed, canDoBatch: g.canDoBatch, version: g.version, } } // SetBatchGenerationRule sets a function to determine next generated batch of history events func (g *EventGenerator) SetBatchGenerationRule( canDoBatchFunc func([]Vertex, []Vertex) bool, ) { g.canDoBatch = canDoBatchFunc } // SetVersion sets the event version func (g *EventGenerator) SetVersion(version int64) { g.version = version } // GetVersion returns event version func (g *EventGenerator) GetVersion() int64 { return g.version } func (g *EventGenerator) generateNextEventBatch() []Vertex { batch := make([]Vertex, 0) switch { case len(g.previousVertices) == 0: // Generate for the first time, get the event candidates from entry vertex group batch = append(batch, g.getEntryVertex()) case len(g.randomEntryVertices) > 0 && g.dice.Intn(len(g.connections)) == 0: // Get the event candidate from random vertex group batch = append(batch, g.getRandomVertex()) default: // Get the event candidates based on context idx := g.getVertexCandidate() batch = append(batch, g.randomNextVertex(idx)...) g.leafVertices = append(g.leafVertices[:idx], g.leafVertices[idx+1:]...) } return batch } func (g *EventGenerator) updateContext( batch []Vertex, ) { g.leafVertices = append(g.leafVertices, batch...) g.previousVertices = append(g.previousVertices, batch...) } func (g *EventGenerator) getEntryVertex() Vertex { if len(g.entryVertices) == 0 { panic("No possible start vertex to go to next step") } nextRange := len(g.entryVertices) nextIdx := g.dice.Intn(nextRange) vertex := g.entryVertices[nextIdx].DeepCopy() vertex.GenerateData(nil) return vertex } func (g *EventGenerator) getRandomVertex() Vertex { if len(g.randomEntryVertices) == 0 { panic("No possible vertex to go to next step") } nextRange := len(g.randomEntryVertices) nextIdx := g.dice.Intn(nextRange) vertex := g.randomEntryVertices[nextIdx].DeepCopy() parentEvent := g.previousVertices[len(g.previousVertices)-1] vertex.GenerateData(parentEvent.GetData(), parentEvent.GetData(), g.version) return vertex } func (g *EventGenerator) getVertexCandidate() int { if len(g.leafVertices) == 0 { panic("No possible vertex to go to next step") } nextRange := len(g.leafVertices) notAvailable := make(map[int]bool) var nextVertexIdx int for len(notAvailable) < nextRange { nextVertexIdx = g.dice.Intn(nextRange) // If the vertex is not accessible at this state, skip it if _, ok := notAvailable[nextVertexIdx]; ok { continue } isAccessible, nextVertexIdx := g.findAccessibleVertex(nextVertexIdx) if isAccessible { return nextVertexIdx } notAvailable[nextVertexIdx] = true } // If all history event cannot be accessible, which means the model is incorrect panic("cannot find available history event to proceed. please check your model") } func (g *EventGenerator) findAccessibleVertex( vertexIndex int, ) (bool, int) { candidate := g.leafVertices[vertexIndex] if g.leafVertices[len(g.leafVertices)-1].IsStrictOnNextVertex() { vertexIndex = len(g.leafVertices) - 1 candidate = g.leafVertices[vertexIndex] } neighbors := g.connections[candidate.GetName()] for _, nextV := range neighbors { if nextV.GetCondition() == nil || nextV.GetCondition()(g.previousVertices) { return true, vertexIndex } } return false, emptyCandidateIndex } func (g *EventGenerator) randomNextVertex( nextVertexIdx int, ) []Vertex { nextVertex := g.leafVertices[nextVertexIdx] count := g.dice.Intn(nextVertex.GetMaxNextVertex()) + 1 res := make([]Vertex, 0) latestVertex := g.previousVertices[len(g.previousVertices)-1] for i := 0; i < count; i++ { endVertex := g.pickRandomVertex(nextVertex) endVertex.GenerateData(nextVertex.GetData(), latestVertex.GetData(), g.version) latestVertex = endVertex res = append(res, endVertex) if _, ok := g.exitVertices[endVertex.GetName()]; ok { res = []Vertex{endVertex} return res } } return res } func (g *EventGenerator) pickRandomVertex( nextVertex Vertex, ) Vertex { neighbors := g.connections[nextVertex.GetName()] neighborsRange := len(neighbors) nextIdx := g.dice.Intn(neighborsRange) for neighbors[nextIdx].GetCondition() != nil && !neighbors[nextIdx].GetCondition()(g.previousVertices) { nextIdx = g.dice.Intn(neighborsRange) } newConnection := neighbors[nextIdx] endVertex := newConnection.GetEndVertex() if newConnection.GetAction() != nil { newConnection.GetAction()() } return endVertex.DeepCopy() } // NewHistoryEventEdge initials a new edge between two HistoryEventVertexes func NewHistoryEventEdge( start Vertex, end Vertex, ) Edge { return &HistoryEventEdge{ startVertex: start, endVertex: end, } } // SetStartVertex sets the start vertex func (c *HistoryEventEdge) SetStartVertex( start Vertex, ) { c.startVertex = start } // GetStartVertex returns the start vertex func (c HistoryEventEdge) GetStartVertex() Vertex { return c.startVertex } // SetEndVertex sets the end vertex func (c *HistoryEventEdge) SetEndVertex(end Vertex) { c.endVertex = end } // GetEndVertex returns the end vertex func (c HistoryEventEdge) GetEndVertex() Vertex { return c.endVertex } // SetCondition sets the condition to access this edge func (c *HistoryEventEdge) SetCondition( condition func(...interface{}) bool, ) { c.condition = condition } // GetCondition returns the condition func (c HistoryEventEdge) GetCondition() func(...interface{}) bool { return c.condition } // SetAction sets an action to perform when the end vertex hits func (c *HistoryEventEdge) SetAction(action func()) { c.action = action } // GetAction returns the action func (c HistoryEventEdge) GetAction() func() { return c.action } // DeepCopy copies a new edge func (c *HistoryEventEdge) DeepCopy() Edge { return &HistoryEventEdge{ startVertex: c.startVertex.DeepCopy(), endVertex: c.endVertex.DeepCopy(), condition: c.condition, action: c.action, } } // NewHistoryEventVertex initials a history event vertex func NewHistoryEventVertex( name string, ) Vertex { return &HistoryEventVertex{ name: name, isStrictOnNextVertex: false, maxNextGeneration: 1, } } // GetName returns the name func (he HistoryEventVertex) GetName() string { return he.name } // SetName sets the name func (he *HistoryEventVertex) SetName( name string, ) { he.name = name } // Equals compares two vertex // func (he *HistoryEventVertex) Equals( // v Vertex, // ) bool { // // return strings.EqualFold(he.name, v.GetName()) && he.data == v.GetData() // } // SetIsStrictOnNextVertex sets if a vertex can be added between the current vertex and its child Vertices func (he *HistoryEventVertex) SetIsStrictOnNextVertex( isStrict bool, ) { he.isStrictOnNextVertex = isStrict } // IsStrictOnNextVertex returns the isStrict flag func (he HistoryEventVertex) IsStrictOnNextVertex() bool { return he.isStrictOnNextVertex } // SetMaxNextVertex sets the max concurrent path can be generated from this vertex func (he *HistoryEventVertex) SetMaxNextVertex( maxNextGeneration int, ) { if maxNextGeneration < 1 { panic("max next vertex number cannot less than 1") } he.maxNextGeneration = maxNextGeneration } // GetMaxNextVertex returns the max concurrent path func (he HistoryEventVertex) GetMaxNextVertex() int { return he.maxNextGeneration } // SetDataFunc sets the data generation function func (he *HistoryEventVertex) SetDataFunc( dataFunc func(...interface{}) interface{}, ) { he.dataFunc = dataFunc } // GetDataFunc returns the data generation function func (he HistoryEventVertex) GetDataFunc() func(...interface{}) interface{} { return he.dataFunc } // GenerateData generates the data and return func (he *HistoryEventVertex) GenerateData( input ...interface{}, ) interface{} { if he.dataFunc == nil { return nil } he.data = he.dataFunc(input...) return he.data } // GetData returns the vertex data func (he HistoryEventVertex) GetData() interface{} { return he.data } // DeepCopy returns the a deep copy of vertex func (he HistoryEventVertex) DeepCopy() Vertex { return &HistoryEventVertex{ name: he.GetName(), isStrictOnNextVertex: he.IsStrictOnNextVertex(), maxNextGeneration: he.GetMaxNextVertex(), dataFunc: he.GetDataFunc(), data: he.GetData(), } } // NewHistoryEventModel initials new history event model func NewHistoryEventModel() Model { return &HistoryEventModel{ edges: make([]Edge, 0), } } // AddEdge adds an edge to the model func (m *HistoryEventModel) AddEdge( edge ...Edge, ) { m.edges = append(m.edges, edge...) } // ListEdges returns all added edges func (m HistoryEventModel) ListEdges() []Edge { return m.edges }