func TestMachineTransition(t *testing.T) { rules := fsm.Ruleset{} rules.AddTransition(fsm.T{"pending", "started"}) rules.AddTransition(fsm.T{"started", "finished"}) some_thing := Thing{State: "pending"} the_machine := fsm.New(fsm.WithRules(rules), fsm.WithSubject(&some_thing)) var err error // should not be able to transition to the current state err = the_machine.Transition("pending") st.Expect(t, err, fsm.InvalidTransition) st.Expect(t, some_thing.State, fsm.State("pending")) // should not be able to skip states err = the_machine.Transition("finished") st.Expect(t, err, fsm.InvalidTransition) st.Expect(t, some_thing.State, fsm.State("pending")) // should be able to transition to the next valid state err = the_machine.Transition("started") st.Expect(t, err, nil) st.Expect(t, some_thing.State, fsm.State("started")) }
func getLocationRules() fsm.Ruleset { rules := fsm.Ruleset{} rules.AddTransition(fsm.T{"town", "forest"}) rules.AddTransition(fsm.T{"forest", "battle"}) rules.AddRule(fsm.T{"town", "forest"}, func(subject fsm.Stater, goal fsm.State) bool { println("You must have enough energy to go to forest.") return true }) return rules }
func BenchmarkRulesetTransitionInvalid(b *testing.B) { // This should be incredibly fast, since fsm.T{"pending", "finished"} // doesn't exist in the Ruleset. We expect some small overhead from creating // the transition to check the internal map, but otherwise, we should be // bumping up against the speed of a map lookup itself. rules := fsm.Ruleset{} rules.AddTransition(fsm.T{"pending", "started"}) rules.AddTransition(fsm.T{"started", "finished"}) some_thing := &Thing{State: "pending"} b.ResetTimer() for i := 0; i < b.N; i++ { rules.Permitted(some_thing, "finished") } }
func TestRulesetParallelGuarding(t *testing.T) { rules := fsm.Ruleset{} rules.AddTransition(fsm.T{"pending", "started"}) rules.AddTransition(fsm.T{"started", "finished"}) // Add two failing rules, the slow should be caught first rules.AddRule(fsm.T{"started", "finished"}, func(subject fsm.Stater, goal fsm.State) bool { time.Sleep(1 * time.Second) t.Error("Slow rule should have been short-circuited") return false }) rules.AddRule(fsm.T{"started", "finished"}, func(subject fsm.Stater, goal fsm.State) bool { return false }) st.Expect(t, rules.Permitted(&Thing{State: "started"}, "finished"), false) }
func BenchmarkRulesetTransitionPermitted(b *testing.B) { // Permitted a transaction requires the transition to be valid and all of its // guards to pass. Since we have to run every guard and there won't be any // short-circuiting, this should actually be a little bit slower as a result, // depending on the number of guards that must pass. rules := fsm.Ruleset{} rules.AddTransition(fsm.T{"pending", "started"}) rules.AddTransition(fsm.T{"started", "finished"}) some_thing := &Thing{State: "started"} b.ResetTimer() for i := 0; i < b.N; i++ { rules.Permitted(some_thing, "finished") } }
func test39() { var err error some_thing := Thing{State: "pending"} // Our subject fmt.Println(some_thing) // Establish some rules for our FSM rules := fsm.Ruleset{} rules.AddTransition(fsm.T{"pending", "started"}) rules.AddTransition(fsm.T{"started", "finished"}) err = some_thing.Apply(&rules).Transition("started") if err != nil { log.Fatal(err) } fmt.Println(some_thing) }
func NewBattleRules() fsm.Ruleset { rules := fsm.Ruleset{} rules.AddTransition(fsm.T{"wait", "attack"}) rules.AddTransition(fsm.T{"wait", "run"}) rules.AddTransition(fsm.T{"wait", "use item"}) rules.AddTransition(fsm.T{"wait", "end"}) rules.AddRule(fsm.T{"wait", "attack"}, func(subject fsm.Stater, goal fsm.State) bool { return true }) return rules }
func BenchmarkRulesetParallelGuarding(b *testing.B) { rules := fsm.Ruleset{} rules.AddTransition(fsm.T{"pending", "started"}) rules.AddTransition(fsm.T{"started", "finished"}) // Add two failing rules, one very slow and the other terribly fast rules.AddRule(fsm.T{"started", "finished"}, func(subject fsm.Stater, goal fsm.State) bool { time.Sleep(1 * time.Second) return false }) rules.AddRule(fsm.T{"started", "finished"}, func(subject fsm.Stater, goal fsm.State) bool { return false }) b.ResetTimer() for i := 0; i < b.N; i++ { rules.Permitted(&Thing{State: "started"}, "finished") } }
func BenchmarkRulesetRuleForbids(b *testing.B) { // Here, we explicity create a transition that is forbidden. This simulates an // otherwise valid transition that would be denied based on a user role or the like. // It should be slower than a standard invalid transition, since we have to // actually execute a function to perform the check. The first guard to // fail (returning false) will short circuit the execution, getting some some speed. rules := fsm.Ruleset{} rules.AddTransition(fsm.T{"pending", "started"}) rules.AddRule(fsm.T{"started", "finished"}, func(subject fsm.Stater, goal fsm.State) bool { return false }) some_thing := &Thing{State: "started"} b.ResetTimer() for i := 0; i < b.N; i++ { rules.Permitted(some_thing, "finished") } }
func NewWindowRules() fsm.Ruleset { rules := fsm.Ruleset{} rules.AddTransition(fsm.T{"title", "character selection"}) rules.AddTransition(fsm.T{"character selection", "game"}) rules.AddTransition(fsm.T{"character selection", "create character"}) rules.AddRule(fsm.T{"title", "character selection"}, func(subject fsm.Stater, goal fsm.State) bool { return true }) return rules }
func createTestCaseRules() fsm.Ruleset { rules := fsm.Ruleset{} rules.AddTransition(fsm.T{DESTROYED, SETUP}) rules.AddTransition(fsm.T{SETUP, PROVISIONED}) rules.AddTransition(fsm.T{PROVISIONED, VERIFIED}) rules.AddTransition(fsm.T{SETUP, FAILED}) rules.AddTransition(fsm.T{PROVISIONED, FAILED}) rules.AddTransition(fsm.T{VERIFIED, FAILED}) rules.AddTransition(fsm.T{SETUP, DESTROYED}) rules.AddTransition(fsm.T{PROVISIONED, DESTROYED}) rules.AddTransition(fsm.T{VERIFIED, DESTROYED}) rules.AddTransition(fsm.T{PROVISIONED, SETUP}) rules.AddTransition(fsm.T{PROVISIONED, PROVISIONED}) rules.AddTransition(fsm.T{VERIFIED, VERIFIED}) rules.AddTransition(fsm.T{FAILED, VERIFIED}) rules.AddTransition(fsm.T{FAILED, PROVISIONED}) rules.AddTransition(fsm.T{FAILED, DESTROYED}) return rules }