Skip to content

Commit

Permalink
Small fixes and a remove statement mutator
Browse files Browse the repository at this point in the history
Fixes #3
  • Loading branch information
zimmski committed Dec 29, 2014
1 parent e9fdfd9 commit 29877a2
Show file tree
Hide file tree
Showing 19 changed files with 484 additions and 12 deletions.
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ cd $GOPATH/src/github.com/zimmski/go-mutesting
go-mutesting --exec "$GOPATH/src/github.com/zimmski/go-mutesting/scripts/simple.sh" --exec-timeout 1 github.com/zimmski/go-mutesting/...
```

The execution of the command outputs for every mutation if it was successfully tested or not. If not, the source code diff is printed out so the mutation can be investigated. The following shows and example for a diff of a mutation for the go-mutesting project itself.
The execution of the command outputs for every mutation if it was successfully tested or not. If not, the source code diff is printed out so the mutation can be investigated. The following shows an example for a diff of a mutation for the go-mutesting project itself.

```diff
@@ -155,7 +155,7 @@
Expand Down Expand Up @@ -48,7 +48,7 @@ The definition of mutation testing is best quoted from Wikipedia:
Although the definition states that the main purpose of mutation testing is finding implementation cases which are not covered by tests, other implementation flaws can be found too. Mutation testing can for example uncover dead and unneeded code.

Mutation testing is also especially interesting for comparing automatically generated test suites with hand written test suites. This was the original intention of go-mutesting which is used to evaluate the generic fuzzing and delta-debugging framework [Tavor](https://github.com/zimmski/tavor).
Mutation testing is also especially interesting for comparing automatically generated test suites with manually written test suites. This was the original intention of go-mutesting which is used to evaluate the generic fuzzing and delta-debugging framework [Tavor](https://github.com/zimmski/tavor).

## <a name="how-do-i-use-go-mutesting"></a>How do I use go-mutesting?

Expand Down Expand Up @@ -102,15 +102,15 @@ The mutation score is 0.750000 (3 passed, 1 failed, 0 skipped, total is 4)

The output shows that four mutations have been found and tested. Three of them passed which means that the test suite failed for these mutations and the mutations were therefore killed. However, one mutation did not fail the test suite. Its source code diff is shown in the output which can be used to investigate the mutation.

The summary also shows the **mutation score** which is an metric on how many mutations are killed by the test suite and therefore states the quality of the test suite. The mutation score is calculated by dividing the amount of all passed mutations with the amount of mutations that passed plus the amount of mutations that failed. A score of 1.0 therefore means that all mutations have been killed.
The summary also shows the **mutation score** which is a metric on how many mutations are killed by the test suite and therefore states the quality of the test suite. The mutation score is calculated by dividing the amount of all passed mutations with the amount of mutations that passed plus the amount of mutations that failed. A score of 1.0 therefore means that all mutations have been killed.

## <a name="write-mutation-exec-commands"></a>How do I write my own mutation exec commands?

A mutation exec command is invoked for every mutation which is necessary to test a mutation. Commands should handle at least the following phases.

1. **Setup** the source to include the mutation.
2. **Test** the source by invoking the test suite and possible other test functionality.
3. **Cleanup** revert all changes and remove all temporary assets.
3. **Cleanup** all changes and remove all temporary assets.
4. **Report** if the mutation was detected.

It is important to note that each invocation should be isolated and therefore stateless. This means that an invocation must not disturb other invocations.
Expand All @@ -119,8 +119,8 @@ The command is given a set of environment variables which define exactly one mut

| Name | Description |
| :-------------- | :------------------------------------------------------------- |
| MUTATE_ORIGINAL | Defines the filepath to the original file which was mutated. |
| MUTATE_CHANGED | Defines the filepath to the mutation of the original file. |
| MUTATE_ORIGINAL | Defines the filename to the original file which was mutated. |
| MUTATE_CHANGED | Defines the filename to the mutation of the original file. |
| MUTATE_TIMEOUT | Defines a timeout which should be honored by the exec command. |

A command must exit with an appropriate exit code.
Expand Down Expand Up @@ -149,6 +149,12 @@ Examples for exec commands can be found in the [scripts](/scripts) directory.
| :------------------ | :--------------------------------------------- |
| expression/remove | Searches for `&&` and <code>\|\|</code> operators and makes each term of the operator irrelevant by using `true` or `false` as replacements. |

### Statement mutators

| Name | Description |
| :------------------ | :--------------------------------------------- |
| statement/remove | Removes assignment, increment, decrement and expression statements in block statements. |

## <a name="write-mutators"></a>How do I write my own mutators?

Each mutator must implement the `Mutator` interface of the [github.com/zimmski/go-mutesting/mutator](https://godoc.org/github.com/zimmski/go-mutesting/mutator#Mutator) package. The methods of the interface are described in detail in the source code documentation.
Expand Down
3 changes: 2 additions & 1 deletion cmd/go-mutesting/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/zimmski/go-mutesting/mutator"
_ "github.com/zimmski/go-mutesting/mutator/branch"
_ "github.com/zimmski/go-mutesting/mutator/expression"
_ "github.com/zimmski/go-mutesting/mutator/statement"
)

const (
Expand Down Expand Up @@ -51,7 +52,7 @@ var opts struct {
} `group:"File options"`

Mutator struct {
DisableMutators []string `long:"disable" description:"Disable mutator or mutators using * as a suffix pattern"`
DisableMutators []string `long:"disable" description:"Disable mutator by their name or using * as a suffix pattern"`
ListMutators bool `long:"list-mutators" description:"List all available mutators"`
} `group:"Mutator options"`

Expand Down
3 changes: 3 additions & 0 deletions example/example.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ func foo() int {

n += bar()

bar()
bar()

return n
}

Expand Down
2 changes: 1 addition & 1 deletion mutator/branch/mutateelse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"github.com/zimmski/go-mutesting/test"
)

func TestMutateElse(t *testing.T) {
func TestMutatorElse(t *testing.T) {
test.Mutator(
t,
NewMutatorElse(),
Expand Down
2 changes: 1 addition & 1 deletion mutator/branch/mutateif_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"github.com/zimmski/go-mutesting/test"
)

func TestMutateIf(t *testing.T) {
func TestMutatorIf(t *testing.T) {
test.Mutator(
t,
NewMutatorIf(),
Expand Down
2 changes: 1 addition & 1 deletion mutator/expression/remove_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"github.com/zimmski/go-mutesting/test"
)

func TestMutateElse(t *testing.T) {
func TestMutatorRemoveTerm(t *testing.T) {
test.Mutator(
t,
NewMutatorRemoveTerm(),
Expand Down
92 changes: 92 additions & 0 deletions mutator/statement/remove.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package statement

import (
"go/ast"
"go/token"

"github.com/zimmski/go-mutesting/mutator"
)

// MutatorRemoveStatement implements a mutator to remove statements
type MutatorRemoveStatement struct{}

// NewMutatorRemoveStatement returns a new instance of a MutatorRemoveStatement mutator
func NewMutatorRemoveStatement() *MutatorRemoveStatement {
return &MutatorRemoveStatement{}
}

func init() {
mutator.Register(MutatorRemoveStatement{}.String(), func() mutator.Mutator {
return NewMutatorRemoveStatement()
})
}

func (m *MutatorRemoveStatement) checkStatement(node ast.Stmt) bool {
switch n := node.(type) {
case *ast.AssignStmt:
if n.Tok != token.DEFINE {
return true
}
case *ast.ExprStmt, *ast.IncDecStmt:
return true
}

return false
}

func (m *MutatorRemoveStatement) check(node ast.Node) (*ast.BlockStmt, uint) {
n, ok := node.(*ast.BlockStmt)
if !ok {
return nil, 0
}

count := uint(0)

for _, ni := range n.List {
if m.checkStatement(ni) {
count++
}
}

return n, 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) {
n, count := m.check(node)
if count == 0 {
changed <- false

return
}

for i, ni := range n.List {
if m.checkStatement(ni) {
old := n.List[i]
n.List[i] = &ast.EmptyStmt{
Semicolon: old.Pos(),
}

changed <- true
<-changed

n.List[i] = old

changed <- true
<-changed
}
}
}

// String implements the String method of the Stringer interface
func (m MutatorRemoveStatement) String() string {
return "statement/remove"
}
16 changes: 16 additions & 0 deletions mutator/statement/remove_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package statement

import (
"testing"

"github.com/zimmski/go-mutesting/test"
)

func TestMutatorRemoveStatement(t *testing.T) {
test.Mutator(
t,
NewMutatorRemoveStatement(),
"../../testdata/statement/remove.go",
9,
)
}
5 changes: 3 additions & 2 deletions test/mutator.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,11 @@ func Mutator(t *testing.T, m mutator.Mutator, testFile string, count uint) {
err = printer.Fprint(buf, fset, f)
assert.Nil(t, err)

changedFile, err := ioutil.ReadFile(fmt.Sprintf("%s.%d.go", testFile, i))
changedFilename := fmt.Sprintf("%s.%d.go", testFile, i)
changedFile, err := ioutil.ReadFile(changedFilename)
assert.Nil(t, err)

assert.Equal(t, string(changedFile), buf.String())
assert.Equal(t, string(changedFile), buf.String(), fmt.Sprintf("For change file %q", changedFilename))

changed <- true

Expand Down
36 changes: 36 additions & 0 deletions testdata/statement/remove.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// +build example-main

package example

func foo() int {
n := 1

for i := 0; i < 3; i++ {
if i == 0 {
n++
} else if i == 1 {
n += 2
} else {
n += 3
}

n++
}

if n < 0 {
n = 0
}

n++

n += bar()

bar()
bar()

return n
}

func bar() int {
return 4
}
34 changes: 34 additions & 0 deletions testdata/statement/remove.go.0.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// +build example-main

package example

func foo() int {
n := 1

for i := 0; i < 3; i++ {
if i == 0 {
n++
} else if i == 1 {
n += 2
} else {
n += 3
}

n++
}

if n < 0 {
n = 0
}

n += bar()

bar()
bar()

return n
}

func bar() int {
return 4
}
34 changes: 34 additions & 0 deletions testdata/statement/remove.go.1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// +build example-main

package example

func foo() int {
n := 1

for i := 0; i < 3; i++ {
if i == 0 {
n++
} else if i == 1 {
n += 2
} else {
n += 3
}

n++
}

if n < 0 {
n = 0
}

n++

bar()
bar()

return n
}

func bar() int {
return 4
}
35 changes: 35 additions & 0 deletions testdata/statement/remove.go.2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// +build example-main

package example

func foo() int {
n := 1

for i := 0; i < 3; i++ {
if i == 0 {
n++
} else if i == 1 {
n += 2
} else {
n += 3
}

n++
}

if n < 0 {
n = 0
}

n++

n += bar()

bar()

return n
}

func bar() int {
return 4
}
Loading

0 comments on commit 29877a2

Please sign in to comment.