summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/mschoch/smat/smat.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/mschoch/smat/smat.go')
-rw-r--r--vendor/github.com/mschoch/smat/smat.go161
1 files changed, 161 insertions, 0 deletions
diff --git a/vendor/github.com/mschoch/smat/smat.go b/vendor/github.com/mschoch/smat/smat.go
new file mode 100644
index 0000000000..f6ea4975f2
--- /dev/null
+++ b/vendor/github.com/mschoch/smat/smat.go
@@ -0,0 +1,161 @@
+// Copyright (c) 2016 Marty Schoch
+
+// 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.
+
+package smat
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "log"
+ "math/rand"
+)
+
+// Logger is a configurable logger used by this package
+// by default output is discarded
+var Logger = log.New(ioutil.Discard, "smat ", log.LstdFlags)
+
+// Context is a container for any user state
+type Context interface{}
+
+// State is a function which describes which action to perform in the event
+// that a particular byte is seen
+type State func(next byte) ActionID
+
+// PercentAction describes the frequency with which an action should occur
+// for example: Action{Percent:10, Action:DonateMoney} means that 10% of
+// the time you should donate money.
+type PercentAction struct {
+ Percent int
+ Action ActionID
+}
+
+// Action is any function which returns the next state to transition to
+// it can optionally mutate the provided context object
+// if any error occurs, it may return an error which will abort execution
+type Action func(Context) (State, error)
+
+// ActionID is a unique identifier for an action
+type ActionID int
+
+// NopAction does nothing and simply continues to the next input
+var NopAction ActionID = -1
+
+// ActionMap is a mapping form ActionID to Action
+type ActionMap map[ActionID]Action
+
+func (a ActionMap) findSetupTeardown(setup, teardown ActionID) (Action, Action, error) {
+ setupFunc, ok := a[setup]
+ if !ok {
+ return nil, nil, ErrSetupMissing
+ }
+ teardownFunc, ok := a[teardown]
+ if !ok {
+ return nil, nil, ErrTeardownMissing
+ }
+ return setupFunc, teardownFunc, nil
+}
+
+// Fuzz runs the fuzzing state machine with the provided context
+// first, the setup action is executed unconditionally
+// the start state is determined by this action
+// actionMap is a lookup table for all actions
+// the data byte slice determines all future state transitions
+// finally, the teardown action is executed unconditionally for cleanup
+func Fuzz(ctx Context, setup, teardown ActionID, actionMap ActionMap, data []byte) int {
+ reader := bytes.NewReader(data)
+ err := runReader(ctx, setup, teardown, actionMap, reader, nil)
+ if err != nil {
+ panic(err)
+ }
+ return 1
+}
+
+// Longevity runs the state machine with the provided context
+// first, the setup action is executed unconditionally
+// the start state is determined by this action
+// actionMap is a lookup table for all actions
+// random bytes are generated to determine all future state transitions
+// finally, the teardown action is executed unconditionally for cleanup
+func Longevity(ctx Context, setup, teardown ActionID, actionMap ActionMap, seed int64, closeChan chan struct{}) error {
+ source := rand.NewSource(seed)
+ return runReader(ctx, setup, teardown, actionMap, rand.New(source), closeChan)
+}
+
+var (
+ // ErrSetupMissing is returned when the setup action cannot be found
+ ErrSetupMissing = fmt.Errorf("setup action missing")
+ // ErrTeardownMissing is returned when the teardown action cannot be found
+ ErrTeardownMissing = fmt.Errorf("teardown action missing")
+ // ErrClosed is returned when the closeChan was closed to cancel the op
+ ErrClosed = fmt.Errorf("closed")
+ // ErrActionNotPossible is returned when an action is encountered in a
+ // FuzzCase that is not possible in the current state
+ ErrActionNotPossible = fmt.Errorf("action not possible in state")
+)
+
+func runReader(ctx Context, setup, teardown ActionID, actionMap ActionMap, r io.Reader, closeChan chan struct{}) error {
+ setupFunc, teardownFunc, err := actionMap.findSetupTeardown(setup, teardown)
+ if err != nil {
+ return err
+ }
+ Logger.Printf("invoking setup action")
+ state, err := setupFunc(ctx)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ Logger.Printf("invoking teardown action")
+ _, _ = teardownFunc(ctx)
+ }()
+
+ reader := bufio.NewReader(r)
+ for next, err := reader.ReadByte(); err == nil; next, err = reader.ReadByte() {
+ select {
+ case <-closeChan:
+ return ErrClosed
+ default:
+ actionID := state(next)
+ action, ok := actionMap[actionID]
+ if !ok {
+ Logger.Printf("no such action defined, continuing")
+ continue
+ }
+ Logger.Printf("invoking action - %d", actionID)
+ state, err = action(ctx)
+ if err != nil {
+ Logger.Printf("it was action %d that returned err %v", actionID, err)
+ return err
+ }
+ }
+ }
+ return err
+}
+
+// PercentExecute interprets the next byte as a random value and normalizes it
+// to values 0-99, it then looks to see which action should be execued based
+// on the action distributions
+func PercentExecute(next byte, pas ...PercentAction) ActionID {
+ percent := int(99 * int(next) / 255)
+
+ sofar := 0
+ for _, pa := range pas {
+ sofar = sofar + pa.Percent
+ if percent < sofar {
+ return pa.Action
+ }
+
+ }
+ return NopAction
+}