diff --git a/mutator/branch/mutatecase.go b/mutator/branch/mutatecase.go index a01f31c..52057e4 100644 --- a/mutator/branch/mutatecase.go +++ b/mutator/branch/mutatecase.go @@ -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 + }, + }, + } } diff --git a/mutator/branch/mutateelse.go b/mutator/branch/mutateelse.go index f87d427..4d4a387 100644 --- a/mutator/branch/mutateelse.go +++ b/mutator/branch/mutateelse.go @@ -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{} @@ -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 + }, + }, + } } diff --git a/mutator/branch/mutateif.go b/mutator/branch/mutateif.go index 01a2dd2..72c88d0 100644 --- a/mutator/branch/mutateif.go +++ b/mutator/branch/mutateif.go @@ -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 + }, + }, + } } diff --git a/mutator/expression/remove.go b/mutator/expression/remove.go index d6e8750..aabdf51 100644 --- a/mutator/expression/remove.go +++ b/mutator/expression/remove.go @@ -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{} @@ -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 @@ -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 + }, + }, + } } diff --git a/mutator/mutation.go b/mutator/mutation.go new file mode 100644 index 0000000..5403cc2 --- /dev/null +++ b/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() +} diff --git a/mutator/mutator.go b/mutator/mutator.go index d1d152a..746591e 100644 --- a/mutator/mutator.go +++ b/mutator/mutator.go @@ -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) diff --git a/mutator/mutator_test.go b/mutator/mutator_test.go index ff4e765..c34000c 100644 --- a/mutator/mutator_test.go +++ b/mutator/mutator_test.go @@ -9,21 +9,17 @@ import ( type mockMutator struct{} -func (m *mockMutator) Check(node ast.Node) uint { +func (m *mockMutator) Mutations(node ast.Node) []Mutation { // do nothing - return 0 -} - -func (m *mockMutator) Mutate(node ast.Node, changed chan bool) { - // do nothing + return nil } func (m *mockMutator) String() string { return "mock" } -func TestMutator(t *testing.T) { +func TestMockMutator(t *testing.T) { // mock is not registered for _, name := range List() { if name == "mock" { diff --git a/mutator/statement/remove.go b/mutator/statement/remove.go index 7dd45aa..d8d0b7d 100644 --- a/mutator/statement/remove.go +++ b/mutator/statement/remove.go @@ -8,6 +8,12 @@ import ( "github.com/zimmski/go-mutesting/mutator" ) +func init() { + mutator.Register(MutatorRemoveStatement{}.String(), func() mutator.Mutator { + return NewMutatorRemoveStatement() + }) +} + // MutatorRemoveStatement implements a mutator to remove statements type MutatorRemoveStatement struct{} @@ -16,10 +22,9 @@ func NewMutatorRemoveStatement() *MutatorRemoveStatement { return &MutatorRemoveStatement{} } -func init() { - mutator.Register(MutatorRemoveStatement{}.String(), func() mutator.Mutator { - return NewMutatorRemoveStatement() - }) +// String implements the String method of the Stringer interface +func (m MutatorRemoveStatement) String() string { + return "statement/remove" } func (m *MutatorRemoveStatement) checkStatement(node ast.Stmt) bool { @@ -35,8 +40,8 @@ func (m *MutatorRemoveStatement) checkStatement(node ast.Stmt) bool { return false } -func (m *MutatorRemoveStatement) check(node ast.Node) uint { - var count uint +// Mutations returns a list of possible mutations for the given node. +func (m *MutatorRemoveStatement) Mutations(node ast.Node) []mutator.Mutation { var l []ast.Stmt switch n := node.(type) { @@ -44,62 +49,25 @@ func (m *MutatorRemoveStatement) check(node ast.Node) uint { l = n.List case *ast.CaseClause: l = n.Body - default: - return 0 - } - - for _, ni := range l { - if m.checkStatement(ni) { - count++ - } - } - - return count -} - -// Check validates how often a node can be mutated by a mutator -func (m *MutatorRemoveStatement) Check(node ast.Node) uint { - count := m.check(node) - - return count -} - -// 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 *MutatorRemoveStatement) Mutate(node ast.Node, changed chan bool) { - count := m.check(node) - if count == 0 { - changed <- false - - return } - var l []ast.Stmt - - switch n := node.(type) { - case *ast.BlockStmt: - l = n.List - case *ast.CaseClause: - l = n.Body - } + var mutations []mutator.Mutation for i, ni := range l { if m.checkStatement(ni) { - old := l[i] - l[i] = astutil.CreateNoopOfStatement(old) - - changed <- true - <-changed - - l[i] = old - - changed <- true - <-changed + li := i + old := l[li] + + mutations = append(mutations, mutator.Mutation{ + Change: func() { + l[li] = astutil.CreateNoopOfStatement(old) + }, + Reset: func() { + l[li] = old + }, + }) } } -} -// String implements the String method of the Stringer interface -func (m MutatorRemoveStatement) String() string { - return "statement/remove" + return mutations } diff --git a/test/mutator.go b/test/mutator.go index 3b2fca1..3d783d9 100644 --- a/test/mutator.go +++ b/test/mutator.go @@ -15,7 +15,7 @@ import ( // Mutator tests a mutator. // It mutates the given original file with the given mutator. Every mutation is then validated with the given changed file. The mutation overall count is validated with the given count. -func Mutator(t *testing.T, m mutator.Mutator, testFile string, count uint) { +func Mutator(t *testing.T, m mutator.Mutator, testFile string, count int) { // test if mutator is not nil assert.NotNil(t, m) @@ -31,20 +31,17 @@ func Mutator(t *testing.T, m mutator.Mutator, testFile string, count uint) { f, fset, err := mutesting.ParseSource(originalFile) assert.Nil(t, err) + // mutate a non relevant node + assert.Nil(t, m.Mutations(f)) + // count the actual mutations n := mutesting.CountWalk(f, m) assert.Equal(t, count, n) - // mutate a non relevant node - changed := make(chan bool) - - go m.Mutate(f, changed) - assert.False(t, <-changed) - // mutate all relevant nodes -> test whole mutation process - changed = mutesting.MutateWalk(f, m) + changed := mutesting.MutateWalk(f, m) - for i := uint(0); i < count; i++ { + for i := 0; i < count; i++ { assert.True(t, <-changed) buf := new(bytes.Buffer) diff --git a/walk.go b/walk.go index 1344b68..4b66b22 100644 --- a/walk.go +++ b/walk.go @@ -10,7 +10,7 @@ import ( // CountWalk returns the number of corresponding mutations for a given mutator. // It traverses the AST of the given node and calls the method Check of the given mutator for every node and sums up the returned counts. After completion of the traversal the final counter is returned. -func CountWalk(node ast.Node, m mutator.Mutator) uint { +func CountWalk(node ast.Node, m mutator.Mutator) int { w := &countWalk{ count: 0, mutator: m, @@ -22,7 +22,7 @@ func CountWalk(node ast.Node, m mutator.Mutator) uint { } type countWalk struct { - count uint + count int mutator mutator.Mutator } @@ -32,9 +32,7 @@ func (w *countWalk) Visit(node ast.Node) ast.Visitor { return w } - if n := w.mutator.Check(node); n > 0 { - w.count += n - } + w.count += len(w.mutator.Mutations(node)) return w } @@ -67,8 +65,14 @@ func (w *mutateWalk) Visit(node ast.Node) ast.Visitor { return w } - if w.mutator.Check(node) > 0 { - w.mutator.Mutate(node, w.changed) + for _, m := range w.mutator.Mutations(node) { + m.Change() + w.changed <- true + <-w.changed + + m.Reset() + w.changed <- true + <-w.changed } return w