Skip to content

Commit

Permalink
Revamp the mutation process
Browse files Browse the repository at this point in the history
Fixes #26
  • Loading branch information
zimmski committed Jun 25, 2016
1 parent f8b3b17 commit 4d277d1
Show file tree
Hide file tree
Showing 10 changed files with 157 additions and 265 deletions.
65 changes: 24 additions & 41 deletions mutator/branch/mutatecase.go
Expand Up @@ -7,61 +7,44 @@ import (
"github.com/zimmski/go-mutesting/mutator"
)

// MutatorCase implements a mutator for case
type MutatorCase struct{}

// NewMutatorCase returns a new instance of a MutatorCase mutator
func NewMutatorCase() *MutatorCase {
return &MutatorCase{}
}

func init() {
mutator.Register(MutatorCase{}.String(), func() mutator.Mutator {
return NewMutatorCase()
})
}

func (m *MutatorCase) check(node ast.Node) (*ast.CaseClause, bool) {
n, ok := node.(*ast.CaseClause)
// MutatorCase implements a mutator for case
type MutatorCase struct{}

return n, ok
// NewMutatorCase returns a new instance of a MutatorCase mutator
func NewMutatorCase() *MutatorCase {
return &MutatorCase{}
}

// Check validates how often a node can be mutated by a mutator
func (m *MutatorCase) Check(node ast.Node) uint {
_, ok := m.check(node)
if !ok {
return 0
}

return 1
// String implements the String method of the Stringer interface
func (m MutatorCase) String() string {
return "branch/case"
}

// Mutate mutates a given node if it can be mutated by the mutator.
// It first checks if the given node can be mutated by the mutator. If the node cannot be mutated, false is send into the given control channel and the method returns. If the node can be mutated, the current state of the node is saved. Afterwards the node is mutated, true is send into the given control channel and the method waits on the channel to continue the process. After receiving a value from the channel the original state of the node is restored, true is send into the given control channel and the method waits on the channel to continue the process. After receiving a value from the channel the method returns which finishes the mutation process.
func (m *MutatorCase) Mutate(node ast.Node, changed chan bool) {
n, ok := m.check(node)
// Mutations returns a list of possible mutations for the given node.
func (m *MutatorCase) Mutations(node ast.Node) []mutator.Mutation {
n, ok := node.(*ast.CaseClause)
if !ok {
changed <- false

return
return nil
}

old := n.Body
n.Body = []ast.Stmt{
astutil.CreateNoopOfStatements(n.Body),
}

changed <- true
<-changed

n.Body = old

changed <- true
<-changed
}

// String implements the String method of the Stringer interface
func (m MutatorCase) String() string {
return "branch/case"
return []mutator.Mutation{
mutator.Mutation{
Change: func() {
n.Body = []ast.Stmt{
astutil.CreateNoopOfStatements(n.Body),
}
},
Reset: func() {
n.Body = old
},
},
}
}
67 changes: 23 additions & 44 deletions mutator/branch/mutateelse.go
Expand Up @@ -7,6 +7,12 @@ import (
"github.com/zimmski/go-mutesting/mutator"
)

func init() {
mutator.Register(MutatorElse{}.String(), func() mutator.Mutator {
return NewMutatorElse()
})
}

// MutatorElse implements a mutator for else branches
type MutatorElse struct{}

Expand All @@ -15,60 +21,33 @@ func NewMutatorElse() *MutatorElse {
return &MutatorElse{}
}

func init() {
mutator.Register(MutatorElse{}.String(), func() mutator.Mutator {
return NewMutatorElse()
})
// String implements the String method of the Stringer interface
func (m MutatorElse) String() string {
return "branch/else"
}

func (m *MutatorElse) check(node ast.Node) (*ast.IfStmt, bool) {
// Mutations returns a list of possible mutations for the given node.
func (m *MutatorElse) Mutations(node ast.Node) []mutator.Mutation {
n, ok := node.(*ast.IfStmt)
if !ok {
return nil, false
return nil
}

// we ignore else ifs and nil blocks
_, ok = n.Else.(*ast.IfStmt)
if ok || n.Else == nil {
return nil, false
}

return n, true
}

// Check validates how often a node can be mutated by a mutator
func (m *MutatorElse) Check(node ast.Node) uint {
_, ok := m.check(node)
if !ok {
return 0
}

return 1
}

// Mutate mutates a given node if it can be mutated by the mutator.
// It first checks if the given node can be mutated by the mutator. If the node cannot be mutated, false is send into the given control channel and the method returns. If the node can be mutated, the current state of the node is saved. Afterwards the node is mutated, true is send into the given control channel and the method waits on the channel to continue the process. After receiving a value from the channel the original state of the node is restored, true is send into the given control channel and the method waits on the channel to continue the process. After receiving a value from the channel the method returns which finishes the mutation process.
func (m *MutatorElse) Mutate(node ast.Node, changed chan bool) {
n, ok := m.check(node)
if !ok {
changed <- false

return
return nil
}

old := n.Else
n.Else = astutil.CreateNoopOfStatement(old)

changed <- true
<-changed

n.Else = old

changed <- true
<-changed
}

// String implements the String method of the Stringer interface
func (m MutatorElse) String() string {
return "branch/else"
return []mutator.Mutation{
mutator.Mutation{
Change: func() {
n.Else = astutil.CreateNoopOfStatement(old)
},
Reset: func() {
n.Else = old
},
},
}
}
65 changes: 24 additions & 41 deletions mutator/branch/mutateif.go
Expand Up @@ -7,61 +7,44 @@ import (
"github.com/zimmski/go-mutesting/mutator"
)

// MutatorIf implements a mutator for if and else if branches
type MutatorIf struct{}

// NewMutatorIf returns a new instance of a MutatorIf mutator
func NewMutatorIf() *MutatorIf {
return &MutatorIf{}
}

func init() {
mutator.Register(MutatorIf{}.String(), func() mutator.Mutator {
return NewMutatorIf()
})
}

func (m *MutatorIf) check(node ast.Node) (*ast.IfStmt, bool) {
n, ok := node.(*ast.IfStmt)

return n, ok
// NewMutatorIf returns a new instance of a MutatorIf mutator
func NewMutatorIf() *MutatorIf {
return &MutatorIf{}
}

// Check validates how often a node can be mutated by a mutator
func (m *MutatorIf) Check(node ast.Node) uint {
_, ok := m.check(node)
if !ok {
return 0
}
// MutatorIf implements a mutator for if and else if branches
type MutatorIf struct{}

return 1
// String implements the String method of the Stringer interface
func (m MutatorIf) String() string {
return "branch/if"
}

// Mutate mutates a given node if it can be mutated by the mutator.
// It first checks if the given node can be mutated by the mutator. If the node cannot be mutated, false is send into the given control channel and the method returns. If the node can be mutated, the current state of the node is saved. Afterwards the node is mutated, true is send into the given control channel and the method waits on the channel to continue the process. After receiving a value from the channel the original state of the node is restored, true is send into the given control channel and the method waits on the channel to continue the process. After receiving a value from the channel the method returns which finishes the mutation process.
func (m *MutatorIf) Mutate(node ast.Node, changed chan bool) {
n, ok := m.check(node)
// Mutations returns a list of possible mutations for the given node.
func (m *MutatorIf) Mutations(node ast.Node) []mutator.Mutation {
n, ok := node.(*ast.IfStmt)
if !ok {
changed <- false

return
return nil
}

old := n.Body.List
n.Body.List = []ast.Stmt{
astutil.CreateNoopOfStatement(n.Body),
}

changed <- true
<-changed

n.Body.List = old

changed <- true
<-changed
}

// String implements the String method of the Stringer interface
func (m MutatorIf) String() string {
return "branch/if"
return []mutator.Mutation{
mutator.Mutation{
Change: func() {
n.Body.List = []ast.Stmt{
astutil.CreateNoopOfStatement(n.Body),
}
},
Reset: func() {
n.Body.List = old
},
},
}
}
86 changes: 31 additions & 55 deletions mutator/expression/remove.go
Expand Up @@ -7,6 +7,12 @@ import (
"github.com/zimmski/go-mutesting/mutator"
)

func init() {
mutator.Register(MutatorRemoveTerm{}.String(), func() mutator.Mutator {
return NewMutatorRemoveTerm()
})
}

// MutatorRemoveTerm implements a mutator to remove expression terms
type MutatorRemoveTerm struct{}

Expand All @@ -15,43 +21,19 @@ func NewMutatorRemoveTerm() *MutatorRemoveTerm {
return &MutatorRemoveTerm{}
}

func init() {
mutator.Register(MutatorRemoveTerm{}.String(), func() mutator.Mutator {
return NewMutatorRemoveTerm()
})
// String implements the String method of the Stringer interface
func (m MutatorRemoveTerm) String() string {
return "expression/remove"
}

func (m *MutatorRemoveTerm) check(node ast.Node) (*ast.BinaryExpr, bool) {
// Mutations returns a list of possible mutations for the given node.
func (m *MutatorRemoveTerm) Mutations(node ast.Node) []mutator.Mutation {
n, ok := node.(*ast.BinaryExpr)
if !ok {
return nil, false
return nil
}

if n.Op != token.LAND && n.Op != token.LOR {
return nil, false
}

return n, true
}

// Check validates how often a node can be mutated by a mutator
func (m *MutatorRemoveTerm) Check(node ast.Node) uint {
_, ok := m.check(node)
if !ok {
return 0
}

return 2
}

// Mutate mutates a given node if it can be mutated by the mutator.
// It first checks if the given node can be mutated by the mutator. If the node cannot be mutated, false is send into the given control channel and the method returns. If the node can be mutated, the current state of the node is saved. Afterwards the node is mutated, true is send into the given control channel and the method waits on the channel to continue the process. After receiving a value from the channel the original state of the node is restored, true is send into the given control channel and the method waits on the channel to continue the process. After receiving a value from the channel the method returns which finishes the mutation process.
func (m *MutatorRemoveTerm) Mutate(node ast.Node, changed chan bool) {
n, ok := m.check(node)
if !ok {
changed <- false

return
return nil
}

var r *ast.Ident
Expand All @@ -66,28 +48,22 @@ func (m *MutatorRemoveTerm) Mutate(node ast.Node, changed chan bool) {
x := n.X
y := n.Y

n.X = r

changed <- true
<-changed

n.X = x

changed <- true
<-changed

n.Y = r

changed <- true
<-changed

n.Y = y

changed <- true
<-changed
}

// String implements the String method of the Stringer interface
func (m MutatorRemoveTerm) String() string {
return "expression/remove"
return []mutator.Mutation{
mutator.Mutation{
Change: func() {
n.X = r
},
Reset: func() {
n.X = x
},
},
mutator.Mutation{
Change: func() {
n.Y = r
},
Reset: func() {
n.Y = y
},
},
}
}
9 changes: 9 additions & 0 deletions mutator/mutation.go
@@ -0,0 +1,9 @@
package mutator

// Mutation defines the behavior of one mutation
type Mutation struct {
// Change is called before executing the exec command.
Change func()
// Reset is called after executing the exec command.
Reset func()
}
7 changes: 2 additions & 5 deletions mutator/mutator.go
Expand Up @@ -10,11 +10,8 @@ import (
type Mutator interface {
fmt.Stringer

// Check validates how often a node can be mutated by a mutator
Check(node ast.Node) uint
// Mutate mutates a given node if it can be mutated by the mutator.
// It first checks if the given node can be mutated by the mutator. If the node cannot be mutated, false is send into the given control channel and the method returns. If the node can be mutated, the current state of the node is saved. Afterwards the node is mutated, true is send into the given control channel and the method waits on the channel to continue the process. After receiving a value from the channel the original state of the node is restored, true is send into the given control channel and the method waits on the channel to continue the process. After receiving a value from the channel the method returns which finishes the mutation process.
Mutate(node ast.Node, changed chan bool)
// Mutations returns a list of possible mutations for the given node.
Mutations(node ast.Node) []Mutation
}

var mutatorLookup = make(map[string]func() Mutator)
Expand Down

0 comments on commit 4d277d1

Please sign in to comment.