├── .vscode
└── settings.json
├── example
├── sub
│ ├── sub.go
│ └── sub_test.go
├── b.go
├── example_test.go
├── a.go
└── example.go
├── mutator
├── mutation.go
├── branch
│ ├── mutateif_test.go
│ ├── mutatecase_test.go
│ ├── mutateelse_test.go
│ ├── mutatecase.go
│ ├── mutateif.go
│ └── mutateelse.go
├── expression
│ ├── remove_test.go
│ ├── comparison_test.go
│ ├── comparison.go
│ └── remove.go
├── statement
│ ├── remove_test.go
│ └── remove.go
├── mutator_test.go
└── mutator.go
├── parse_test.go
├── cmd
└── go-mutesting
│ ├── go-mutesting-bash_completion.sh
│ ├── main_test.go
│ └── main.go
├── testdata
├── branch
│ ├── mutateif.go
│ ├── mutateelse.go
│ ├── mutateelse.go.0.go
│ ├── mutateif.go.1.go
│ ├── mutatecase.go
│ ├── mutateif.go.0.go
│ ├── mutatecase.go.0.go
│ ├── mutatecase.go.1.go
│ └── mutatecase.go.2.go
├── expression
│ ├── remove.go.2.go
│ ├── remove.go
│ ├── remove.go.0.go
│ ├── remove.go.1.go
│ ├── remove.go.3.go
│ ├── remove.go.4.go
│ ├── remove.go.5.go
│ ├── comparison.go
│ ├── comparison.go.2.go
│ ├── comparison.go.3.go
│ ├── comparison.go.0.go
│ └── comparison.go.1.go
└── statement
│ ├── remove.go.16.go
│ ├── remove.go.14.go
│ ├── remove.go.2.go
│ ├── remove.go.3.go
│ ├── remove.go.1.go
│ ├── remove.go
│ ├── remove.go.0.go
│ ├── remove.go.4.go
│ ├── remove.go.5.go
│ ├── remove.go.12.go
│ ├── remove.go.13.go
│ ├── remove.go.15.go
│ ├── remove.go.6.go
│ ├── remove.go.7.go
│ ├── remove.go.8.go
│ ├── remove.go.9.go
│ ├── remove.go.10.go
│ └── remove.go.11.go
├── .gitignore
├── .travis.yml
├── go.mod
├── astutil
├── create.go
└── query.go
├── scripts
├── lint.sh
└── exec
│ ├── test-current-directory.sh
│ └── test-mutated-package.sh
├── LICENSE
├── Makefile
├── test
└── mutator.go
├── parse.go
├── walk.go
├── go.sum
└── README.md
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "go.formatFlags": [
3 | "-s"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/example/sub/sub.go:
--------------------------------------------------------------------------------
1 | package sub
2 |
3 | func baz() int {
4 | i := 1
5 | i = i + i
6 |
7 | return i
8 | }
9 |
--------------------------------------------------------------------------------
/example/b.go:
--------------------------------------------------------------------------------
1 | package example
2 |
3 | import (
4 | "net/http"
5 | )
6 |
7 | func fooB() (a A, b http.Header) {
8 | a, b = A{}, http.Header{}
9 |
10 | return a, b
11 | }
12 |
--------------------------------------------------------------------------------
/example/sub/sub_test.go:
--------------------------------------------------------------------------------
1 | package sub
2 |
3 | import (
4 | "testing"
5 |
6 | . "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestBaz(t *testing.T) {
10 | Equal(t, baz(), 2)
11 | }
12 |
--------------------------------------------------------------------------------
/example/example_test.go:
--------------------------------------------------------------------------------
1 | package example
2 |
3 | import (
4 | "testing"
5 |
6 | . "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestFoo(t *testing.T) {
10 | Equal(t, foo(), 16)
11 | }
12 |
--------------------------------------------------------------------------------
/example/a.go:
--------------------------------------------------------------------------------
1 | package example
2 |
3 | import (
4 | "net/http"
5 | )
6 |
7 | type A struct{}
8 |
9 | func fooA() (a A, b http.Header) {
10 | _, _, _ = a, b, http.Header{}
11 |
12 | return a, b
13 | }
14 |
--------------------------------------------------------------------------------
/mutator/mutation.go:
--------------------------------------------------------------------------------
1 | package mutator
2 |
3 | // Mutation defines the behavior of one mutation
4 | type Mutation struct {
5 | // Change is called before executing the exec command.
6 | Change func()
7 | // Reset is called after executing the exec command.
8 | Reset func()
9 | }
10 |
--------------------------------------------------------------------------------
/parse_test.go:
--------------------------------------------------------------------------------
1 | package mutesting
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestParseAndTypeCheckFileTypeCheckWholePackage(t *testing.T) {
10 | _, _, _, _, err := ParseAndTypeCheckFile("astutil/create.go")
11 | assert.Nil(t, err)
12 | }
13 |
--------------------------------------------------------------------------------
/cmd/go-mutesting/go-mutesting-bash_completion.sh:
--------------------------------------------------------------------------------
1 | #/bin/sh
2 |
3 | _go_mutesting() {
4 | args=("${COMP_WORDS[@]:1:$COMP_CWORD}")
5 |
6 | local IFS=$'\n'
7 | COMPREPLY=($(GO_FLAGS_COMPLETION=1 ${COMP_WORDS[0]} "${args[@]}"))
8 | return 1
9 | }
10 |
11 | complete -F _go_mutesting go-mutesting
12 |
--------------------------------------------------------------------------------
/mutator/branch/mutateif_test.go:
--------------------------------------------------------------------------------
1 | package branch
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/zimmski/go-mutesting/test"
7 | )
8 |
9 | func TestMutatorIf(t *testing.T) {
10 | test.Mutator(
11 | t,
12 | MutatorIf,
13 | "../../testdata/branch/mutateif.go",
14 | 2,
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/mutator/branch/mutatecase_test.go:
--------------------------------------------------------------------------------
1 | package branch
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/zimmski/go-mutesting/test"
7 | )
8 |
9 | func TestMutatorCase(t *testing.T) {
10 | test.Mutator(
11 | t,
12 | MutatorCase,
13 | "../../testdata/branch/mutatecase.go",
14 | 3,
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/mutator/branch/mutateelse_test.go:
--------------------------------------------------------------------------------
1 | package branch
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/zimmski/go-mutesting/test"
7 | )
8 |
9 | func TestMutatorElse(t *testing.T) {
10 | test.Mutator(
11 | t,
12 | MutatorElse,
13 | "../../testdata/branch/mutateelse.go",
14 | 1,
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/mutator/expression/remove_test.go:
--------------------------------------------------------------------------------
1 | package expression
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/zimmski/go-mutesting/test"
7 | )
8 |
9 | func TestMutatorRemoveTerm(t *testing.T) {
10 | test.Mutator(
11 | t,
12 | MutatorRemoveTerm,
13 | "../../testdata/expression/remove.go",
14 | 6,
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/mutator/expression/comparison_test.go:
--------------------------------------------------------------------------------
1 | package expression
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/zimmski/go-mutesting/test"
7 | )
8 |
9 | func TestMutatorComparison(t *testing.T) {
10 | test.Mutator(
11 | t,
12 | MutatorComparison,
13 | "../../testdata/expression/comparison.go",
14 | 4,
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/mutator/statement/remove_test.go:
--------------------------------------------------------------------------------
1 | package statement
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/zimmski/go-mutesting/test"
7 | )
8 |
9 | func TestMutatorRemoveStatement(t *testing.T) {
10 | test.Mutator(
11 | t,
12 | MutatorRemoveStatement,
13 | "../../testdata/statement/remove.go",
14 | 17,
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/testdata/branch/mutateif.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | )
8 |
9 | func main() {
10 | i := 1
11 |
12 | for i != 4 {
13 | if i == 1 {
14 | fmt.Println(i)
15 | } else if i == 2 {
16 | fmt.Println(i * 2)
17 | } else {
18 | fmt.Println(i * 3)
19 | }
20 |
21 | i++
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/testdata/branch/mutateelse.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | )
8 |
9 | func main() {
10 | i := 1
11 |
12 | for i != 4 {
13 | if i == 1 {
14 | fmt.Println(i)
15 | } else if i == 2 {
16 | fmt.Println(i * 2)
17 | } else {
18 | fmt.Println(i * 3)
19 | }
20 |
21 | i++
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/testdata/branch/mutateelse.go.0.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | )
8 |
9 | func main() {
10 | i := 1
11 |
12 | for i != 4 {
13 | if i == 1 {
14 | fmt.Println(i)
15 | } else if i == 2 {
16 | fmt.Println(i * 2)
17 | } else {
18 | _, _ = fmt.Println, i
19 | }
20 |
21 | i++
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/testdata/branch/mutateif.go.1.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | )
8 |
9 | func main() {
10 | i := 1
11 |
12 | for i != 4 {
13 | if i == 1 {
14 | fmt.Println(i)
15 | } else if i == 2 {
16 | _, _ = fmt.Println, i
17 | } else {
18 | fmt.Println(i * 3)
19 | }
20 |
21 | i++
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/testdata/expression/remove.go.2.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | )
8 |
9 | func main() {
10 | i := 1
11 |
12 | for i != 4 {
13 | if i >= 1 && i <= 1 {
14 | fmt.Println(i)
15 | } else if false || i*1 == 1+1 {
16 | fmt.Println(i * 2)
17 | } else {
18 |
19 | }
20 |
21 | i++
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/testdata/branch/mutatecase.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | )
8 |
9 | func main() {
10 | i := 1
11 |
12 | for i != 4 {
13 | switch {
14 | case i == 1:
15 | fmt.Println(i)
16 | case i == 2:
17 | fmt.Println(i * 2)
18 | default:
19 | fmt.Println(i * 3)
20 | }
21 |
22 | i++
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/testdata/branch/mutateif.go.0.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | )
8 |
9 | func main() {
10 | i := 1
11 |
12 | for i != 4 {
13 | if i == 1 {
14 | _, _ = fmt.Println, i
15 | } else if i == 2 {
16 | fmt.Println(i * 2)
17 | } else {
18 | fmt.Println(i * 3)
19 | }
20 |
21 | i++
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/testdata/expression/remove.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | )
8 |
9 | func main() {
10 | i := 1
11 |
12 | for i != 4 {
13 | if i >= 1 && i <= 1 {
14 | fmt.Println(i)
15 | } else if (i >= 2 && i <= 2) || i*1 == 1+1 {
16 | fmt.Println(i * 2)
17 | } else {
18 |
19 | }
20 |
21 | i++
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/testdata/expression/remove.go.0.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | )
8 |
9 | func main() {
10 | i := 1
11 |
12 | for i != 4 {
13 | if true && i <= 1 {
14 | fmt.Println(i)
15 | } else if (i >= 2 && i <= 2) || i*1 == 1+1 {
16 | fmt.Println(i * 2)
17 | } else {
18 |
19 | }
20 |
21 | i++
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/testdata/expression/remove.go.1.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | )
8 |
9 | func main() {
10 | i := 1
11 |
12 | for i != 4 {
13 | if i >= 1 && true {
14 | fmt.Println(i)
15 | } else if (i >= 2 && i <= 2) || i*1 == 1+1 {
16 | fmt.Println(i * 2)
17 | } else {
18 |
19 | }
20 |
21 | i++
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/testdata/expression/remove.go.3.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | )
8 |
9 | func main() {
10 | i := 1
11 |
12 | for i != 4 {
13 | if i >= 1 && i <= 1 {
14 | fmt.Println(i)
15 | } else if (i >= 2 && i <= 2) || false {
16 | fmt.Println(i * 2)
17 | } else {
18 |
19 | }
20 |
21 | i++
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/testdata/expression/remove.go.4.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | )
8 |
9 | func main() {
10 | i := 1
11 |
12 | for i != 4 {
13 | if i >= 1 && i <= 1 {
14 | fmt.Println(i)
15 | } else if (true && i <= 2) || i*1 == 1+1 {
16 | fmt.Println(i * 2)
17 | } else {
18 |
19 | }
20 |
21 | i++
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/testdata/expression/remove.go.5.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | )
8 |
9 | func main() {
10 | i := 1
11 |
12 | for i != 4 {
13 | if i >= 1 && i <= 1 {
14 | fmt.Println(i)
15 | } else if (i >= 2 && true) || i*1 == 1+1 {
16 | fmt.Println(i * 2)
17 | } else {
18 |
19 | }
20 |
21 | i++
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/testdata/branch/mutatecase.go.0.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | )
8 |
9 | func main() {
10 | i := 1
11 |
12 | for i != 4 {
13 | switch {
14 | case i == 1:
15 | _, _ = fmt.Println, i
16 | case i == 2:
17 | fmt.Println(i * 2)
18 | default:
19 | fmt.Println(i * 3)
20 | }
21 |
22 | i++
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/testdata/branch/mutatecase.go.1.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | )
8 |
9 | func main() {
10 | i := 1
11 |
12 | for i != 4 {
13 | switch {
14 | case i == 1:
15 | fmt.Println(i)
16 | case i == 2:
17 | _, _ = fmt.Println, i
18 | default:
19 | fmt.Println(i * 3)
20 | }
21 |
22 | i++
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/testdata/branch/mutatecase.go.2.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | )
8 |
9 | func main() {
10 | i := 1
11 |
12 | for i != 4 {
13 | switch {
14 | case i == 1:
15 | fmt.Println(i)
16 | case i == 2:
17 | fmt.Println(i * 2)
18 | default:
19 | _, _ = fmt.Println, i
20 | }
21 |
22 | i++
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
2 | *.o
3 | *.a
4 | *.so
5 |
6 | # Folders
7 | _obj
8 | _test
9 |
10 | # Architecture specific extensions/prefixes
11 | *.[568vq]
12 | [568vq].out
13 |
14 | *.cgo1.go
15 | *.cgo2.c
16 | _cgo_defun.c
17 | _cgo_gotypes.go
18 | _cgo_export.*
19 |
20 | _testmain.go
21 |
22 | *.exe
23 | *.test
24 | *.prof
25 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 |
3 | os:
4 | - linux
5 | - osx
6 |
7 | go:
8 | - 1.11.x
9 | - 1.12.x
10 |
11 | env:
12 | global:
13 | - GO111MODULE: "on"
14 |
15 | install:
16 | - make install-dependencies
17 | - make install-tools
18 | - make install
19 |
20 | script:
21 | - make lint
22 | - make test-verbose-with-coverage
23 | - gover
24 | - if [ "$TRAVIS_SECURE_ENV_VARS" = "true" ]; then goveralls -coverprofile=gover.coverprofile -service=travis-ci; fi
25 |
--------------------------------------------------------------------------------
/testdata/expression/comparison.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | func main() {
8 | if 1 > 2 {
9 | fmt.Printf("1 is greater than 2!")
10 | }
11 |
12 | if 1 < 2 {
13 | fmt.Printf("1 is less than 2!")
14 | }
15 |
16 | if 1 <= 2 {
17 | fmt.Printf("1 is less than or equal to 2!")
18 | }
19 |
20 | if 1 >= 2 {
21 | fmt.Printf("1 is greater than or equal to 2!")
22 | }
23 |
24 | if 1 == 2 {
25 | fmt.Print("1 is equal to 2!")
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/testdata/expression/comparison.go.2.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | func main() {
8 | if 1 > 2 {
9 | fmt.Printf("1 is greater than 2!")
10 | }
11 |
12 | if 1 < 2 {
13 | fmt.Printf("1 is less than 2!")
14 | }
15 |
16 | if 1 < 2 {
17 | fmt.Printf("1 is less than or equal to 2!")
18 | }
19 |
20 | if 1 >= 2 {
21 | fmt.Printf("1 is greater than or equal to 2!")
22 | }
23 |
24 | if 1 == 2 {
25 | fmt.Print("1 is equal to 2!")
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/testdata/expression/comparison.go.3.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | func main() {
8 | if 1 > 2 {
9 | fmt.Printf("1 is greater than 2!")
10 | }
11 |
12 | if 1 < 2 {
13 | fmt.Printf("1 is less than 2!")
14 | }
15 |
16 | if 1 <= 2 {
17 | fmt.Printf("1 is less than or equal to 2!")
18 | }
19 |
20 | if 1 > 2 {
21 | fmt.Printf("1 is greater than or equal to 2!")
22 | }
23 |
24 | if 1 == 2 {
25 | fmt.Print("1 is equal to 2!")
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/testdata/expression/comparison.go.0.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | func main() {
8 | if 1 >= 2 {
9 | fmt.Printf("1 is greater than 2!")
10 | }
11 |
12 | if 1 < 2 {
13 | fmt.Printf("1 is less than 2!")
14 | }
15 |
16 | if 1 <= 2 {
17 | fmt.Printf("1 is less than or equal to 2!")
18 | }
19 |
20 | if 1 >= 2 {
21 | fmt.Printf("1 is greater than or equal to 2!")
22 | }
23 |
24 | if 1 == 2 {
25 | fmt.Print("1 is equal to 2!")
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/testdata/expression/comparison.go.1.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | func main() {
8 | if 1 > 2 {
9 | fmt.Printf("1 is greater than 2!")
10 | }
11 |
12 | if 1 <= 2 {
13 | fmt.Printf("1 is less than 2!")
14 | }
15 |
16 | if 1 <= 2 {
17 | fmt.Printf("1 is less than or equal to 2!")
18 | }
19 |
20 | if 1 >= 2 {
21 | fmt.Printf("1 is greater than or equal to 2!")
22 | }
23 |
24 | if 1 == 2 {
25 | fmt.Print("1 is equal to 2!")
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/example/example.go:
--------------------------------------------------------------------------------
1 | package example
2 |
3 | func foo() int {
4 | n := 1
5 |
6 | for i := 0; i < 3; i++ {
7 | if i == 0 {
8 | n++
9 | } else if i*1 == 2-1 {
10 | n += 2
11 | } else {
12 | n += 3
13 | }
14 |
15 | n++
16 | }
17 |
18 | if n < 0 {
19 | n = 0
20 | }
21 |
22 | n++
23 |
24 | n += bar()
25 |
26 | bar()
27 | bar()
28 |
29 | switch {
30 | case n < 20:
31 | n++
32 | case n > 20:
33 | n--
34 | default:
35 | n = 0
36 | }
37 |
38 | skip := true
39 | if true {
40 | _ = skip
41 | }
42 |
43 | return n
44 | }
45 |
46 | func bar() int {
47 | return 4
48 | }
49 |
50 | func baz() int {
51 | i := 1
52 | i = i + i
53 |
54 | return i
55 | }
56 |
--------------------------------------------------------------------------------
/mutator/branch/mutatecase.go:
--------------------------------------------------------------------------------
1 | package branch
2 |
3 | import (
4 | "go/ast"
5 | "go/types"
6 |
7 | "github.com/zimmski/go-mutesting/astutil"
8 | "github.com/zimmski/go-mutesting/mutator"
9 | )
10 |
11 | func init() {
12 | mutator.Register("branch/case", MutatorCase)
13 | }
14 |
15 | // MutatorCase implements a mutator for case clauses.
16 | func MutatorCase(pkg *types.Package, info *types.Info, node ast.Node) []mutator.Mutation {
17 | n, ok := node.(*ast.CaseClause)
18 | if !ok {
19 | return nil
20 | }
21 |
22 | old := n.Body
23 |
24 | return []mutator.Mutation{
25 | {
26 | Change: func() {
27 | n.Body = []ast.Stmt{
28 | astutil.CreateNoopOfStatements(pkg, info, n.Body),
29 | }
30 | },
31 | Reset: func() {
32 | n.Body = old
33 | },
34 | },
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/mutator/branch/mutateif.go:
--------------------------------------------------------------------------------
1 | package branch
2 |
3 | import (
4 | "go/ast"
5 | "go/types"
6 |
7 | "github.com/zimmski/go-mutesting/astutil"
8 | "github.com/zimmski/go-mutesting/mutator"
9 | )
10 |
11 | func init() {
12 | mutator.Register("branch/if", MutatorIf)
13 | }
14 |
15 | // MutatorIf implements a mutator for if and else if branches.
16 | func MutatorIf(pkg *types.Package, info *types.Info, node ast.Node) []mutator.Mutation {
17 | n, ok := node.(*ast.IfStmt)
18 | if !ok {
19 | return nil
20 | }
21 |
22 | old := n.Body.List
23 |
24 | return []mutator.Mutation{
25 | {
26 | Change: func() {
27 | n.Body.List = []ast.Stmt{
28 | astutil.CreateNoopOfStatement(pkg, info, n.Body),
29 | }
30 | },
31 | Reset: func() {
32 | n.Body.List = old
33 | },
34 | },
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/mutator/branch/mutateelse.go:
--------------------------------------------------------------------------------
1 | package branch
2 |
3 | import (
4 | "go/ast"
5 | "go/types"
6 |
7 | "github.com/zimmski/go-mutesting/astutil"
8 | "github.com/zimmski/go-mutesting/mutator"
9 | )
10 |
11 | func init() {
12 | mutator.Register("branch/else", MutatorElse)
13 | }
14 |
15 | // MutatorElse implements a mutator for else branches.
16 | func MutatorElse(pkg *types.Package, info *types.Info, node ast.Node) []mutator.Mutation {
17 | n, ok := node.(*ast.IfStmt)
18 | if !ok {
19 | return nil
20 | }
21 | // We ignore else ifs and nil blocks
22 | _, ok = n.Else.(*ast.IfStmt)
23 | if ok || n.Else == nil {
24 | return nil
25 | }
26 |
27 | old := n.Else
28 |
29 | return []mutator.Mutation{
30 | {
31 | Change: func() {
32 | n.Else = astutil.CreateNoopOfStatement(pkg, info, old)
33 | },
34 | Reset: func() {
35 | n.Else = old
36 | },
37 | },
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/zimmski/go-mutesting
2 |
3 | go 1.10
4 |
5 | require (
6 | github.com/davecgh/go-spew v1.1.0
7 | github.com/hpcloud/tail v1.0.0 // indirect
8 | github.com/jessevdk/go-flags v1.4.0
9 | github.com/kisielk/errcheck v1.2.0 // indirect
10 | github.com/mattn/goveralls v0.0.3 // indirect
11 | github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5 // indirect
12 | github.com/onsi/ginkgo v1.10.2 // indirect
13 | github.com/pmezard/go-difflib v1.0.0
14 | github.com/stretchr/testify v1.4.0
15 | github.com/zimmski/go-tool v0.0.0-20150119110811-2dfdc9ac8439
16 | github.com/zimmski/osutil v0.0.0-20190128123334-0d0b3ca231ac
17 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de // indirect
18 | golang.org/x/tools v0.0.0-20191018212557-ed542cd5b28a
19 | gopkg.in/fsnotify.v1 v1.4.7 // indirect
20 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
21 | gopkg.in/yaml.v2 v2.2.2
22 | honnef.co/go/tools v0.0.1-2019.2.3 // indirect
23 | )
24 |
--------------------------------------------------------------------------------
/mutator/expression/comparison.go:
--------------------------------------------------------------------------------
1 | package expression
2 |
3 | import (
4 | "go/ast"
5 | "go/token"
6 | "go/types"
7 |
8 | "github.com/zimmski/go-mutesting/mutator"
9 | )
10 |
11 | func init() {
12 | mutator.Register("expression/comparison", MutatorComparison)
13 | }
14 |
15 | var comparisonMutations = map[token.Token]token.Token{
16 | token.LSS: token.LEQ,
17 | token.LEQ: token.LSS,
18 | token.GTR: token.GEQ,
19 | token.GEQ: token.GTR,
20 | }
21 |
22 | // MutatorComparison implements a mutator to change comparisons.
23 | func MutatorComparison(pkg *types.Package, info *types.Info, node ast.Node) []mutator.Mutation {
24 | n, ok := node.(*ast.BinaryExpr)
25 | if !ok {
26 | return nil
27 | }
28 |
29 | o := n.Op
30 | r, ok := comparisonMutations[n.Op]
31 | if !ok {
32 | return nil
33 | }
34 |
35 | return []mutator.Mutation{
36 | {
37 | Change: func() {
38 | n.Op = r
39 | },
40 | Reset: func() {
41 | n.Op = o
42 | },
43 | },
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/astutil/create.go:
--------------------------------------------------------------------------------
1 | package astutil
2 |
3 | import (
4 | "go/ast"
5 | "go/token"
6 | "go/types"
7 | )
8 |
9 | // CreateNoopOfStatement creates a syntactically safe noop statement out of a given statement.
10 | func CreateNoopOfStatement(pkg *types.Package, info *types.Info, stmt ast.Stmt) ast.Stmt {
11 | return CreateNoopOfStatements(pkg, info, []ast.Stmt{stmt})
12 | }
13 |
14 | // CreateNoopOfStatements creates a syntactically safe noop statement out of a given statement.
15 | func CreateNoopOfStatements(pkg *types.Package, info *types.Info, stmts []ast.Stmt) ast.Stmt {
16 | var ids []ast.Expr
17 | for _, stmt := range stmts {
18 | ids = append(ids, IdentifiersInStatement(pkg, info, stmt)...)
19 | }
20 |
21 | if len(ids) == 0 {
22 | return &ast.EmptyStmt{
23 | Semicolon: token.NoPos,
24 | }
25 | }
26 |
27 | lhs := make([]ast.Expr, len(ids))
28 | for i := range ids {
29 | lhs[i] = ast.NewIdent("_")
30 | }
31 |
32 | return &ast.AssignStmt{
33 | Lhs: lhs,
34 | Rhs: ids,
35 | Tok: token.ASSIGN,
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/testdata/statement/remove.go.16.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package example
4 |
5 | import (
6 | "fmt"
7 | "net/http"
8 | )
9 |
10 | func foo() int {
11 | n := 1
12 |
13 | for i := 0; i < 3; i++ {
14 | if i == 0 {
15 | n++
16 | } else if i == 1 {
17 | n += 2
18 | } else {
19 | n += 3
20 | }
21 |
22 | n++
23 | }
24 |
25 | if n < 0 {
26 | n = 0
27 | }
28 |
29 | n++
30 |
31 | n += bar()
32 |
33 | bar()
34 | bar()
35 |
36 | switch {
37 | case n < 20:
38 | n++
39 | case n > 20:
40 | n--
41 | default:
42 | n = 0
43 | fmt.Println(n)
44 | func() {}()
45 | }
46 |
47 | var x = 0
48 | x++
49 |
50 | return n
51 | }
52 |
53 | func bar() int {
54 | return 4
55 | }
56 |
57 | func statementRemoveStructInitialization() (a http.Header, b error) {
58 | var err error
59 |
60 | a, b = http.Header{}, err
61 |
62 | return
63 | }
64 |
65 | func statementRemoveStringArrayMap() map[string][]string {
66 | hash := "ok"
67 | var hdr = make(map[string][]string)
68 | _, _ = hdr, hash
69 |
70 | return hdr
71 | }
72 |
--------------------------------------------------------------------------------
/testdata/statement/remove.go.14.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package example
4 |
5 | import (
6 | "fmt"
7 | "net/http"
8 | )
9 |
10 | func foo() int {
11 | n := 1
12 |
13 | for i := 0; i < 3; i++ {
14 | if i == 0 {
15 | n++
16 | } else if i == 1 {
17 | n += 2
18 | } else {
19 | n += 3
20 | }
21 |
22 | n++
23 | }
24 |
25 | if n < 0 {
26 | n = 0
27 | }
28 |
29 | n++
30 |
31 | n += bar()
32 |
33 | bar()
34 | bar()
35 |
36 | switch {
37 | case n < 20:
38 | n++
39 | case n > 20:
40 | n--
41 | default:
42 | n = 0
43 | fmt.Println(n)
44 |
45 | }
46 |
47 | var x = 0
48 | x++
49 |
50 | return n
51 | }
52 |
53 | func bar() int {
54 | return 4
55 | }
56 |
57 | func statementRemoveStructInitialization() (a http.Header, b error) {
58 | var err error
59 |
60 | a, b = http.Header{}, err
61 |
62 | return
63 | }
64 |
65 | func statementRemoveStringArrayMap() map[string][]string {
66 | hash := "ok"
67 | var hdr = make(map[string][]string)
68 |
69 | hdr["Hash"] = []string{hash}
70 |
71 | return hdr
72 | }
73 |
--------------------------------------------------------------------------------
/testdata/statement/remove.go.2.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package example
4 |
5 | import (
6 | "fmt"
7 | "net/http"
8 | )
9 |
10 | func foo() int {
11 | n := 1
12 |
13 | for i := 0; i < 3; i++ {
14 | if i == 0 {
15 | n++
16 | } else if i == 1 {
17 | n += 2
18 | } else {
19 | n += 3
20 | }
21 |
22 | n++
23 | }
24 |
25 | if n < 0 {
26 | n = 0
27 | }
28 |
29 | n++
30 |
31 | n += bar()
32 |
33 | bar()
34 |
35 | switch {
36 | case n < 20:
37 | n++
38 | case n > 20:
39 | n--
40 | default:
41 | n = 0
42 | fmt.Println(n)
43 | func() {}()
44 | }
45 |
46 | var x = 0
47 | x++
48 |
49 | return n
50 | }
51 |
52 | func bar() int {
53 | return 4
54 | }
55 |
56 | func statementRemoveStructInitialization() (a http.Header, b error) {
57 | var err error
58 |
59 | a, b = http.Header{}, err
60 |
61 | return
62 | }
63 |
64 | func statementRemoveStringArrayMap() map[string][]string {
65 | hash := "ok"
66 | var hdr = make(map[string][]string)
67 |
68 | hdr["Hash"] = []string{hash}
69 |
70 | return hdr
71 | }
72 |
--------------------------------------------------------------------------------
/testdata/statement/remove.go.3.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package example
4 |
5 | import (
6 | "fmt"
7 | "net/http"
8 | )
9 |
10 | func foo() int {
11 | n := 1
12 |
13 | for i := 0; i < 3; i++ {
14 | if i == 0 {
15 | n++
16 | } else if i == 1 {
17 | n += 2
18 | } else {
19 | n += 3
20 | }
21 |
22 | n++
23 | }
24 |
25 | if n < 0 {
26 | n = 0
27 | }
28 |
29 | n++
30 |
31 | n += bar()
32 |
33 | bar()
34 |
35 | switch {
36 | case n < 20:
37 | n++
38 | case n > 20:
39 | n--
40 | default:
41 | n = 0
42 | fmt.Println(n)
43 | func() {}()
44 | }
45 |
46 | var x = 0
47 | x++
48 |
49 | return n
50 | }
51 |
52 | func bar() int {
53 | return 4
54 | }
55 |
56 | func statementRemoveStructInitialization() (a http.Header, b error) {
57 | var err error
58 |
59 | a, b = http.Header{}, err
60 |
61 | return
62 | }
63 |
64 | func statementRemoveStringArrayMap() map[string][]string {
65 | hash := "ok"
66 | var hdr = make(map[string][]string)
67 |
68 | hdr["Hash"] = []string{hash}
69 |
70 | return hdr
71 | }
72 |
--------------------------------------------------------------------------------
/testdata/statement/remove.go.1.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package example
4 |
5 | import (
6 | "fmt"
7 | "net/http"
8 | )
9 |
10 | func foo() int {
11 | n := 1
12 |
13 | for i := 0; i < 3; i++ {
14 | if i == 0 {
15 | n++
16 | } else if i == 1 {
17 | n += 2
18 | } else {
19 | n += 3
20 | }
21 |
22 | n++
23 | }
24 |
25 | if n < 0 {
26 | n = 0
27 | }
28 |
29 | n++
30 | _ = n
31 |
32 | bar()
33 | bar()
34 |
35 | switch {
36 | case n < 20:
37 | n++
38 | case n > 20:
39 | n--
40 | default:
41 | n = 0
42 | fmt.Println(n)
43 | func() {}()
44 | }
45 |
46 | var x = 0
47 | x++
48 |
49 | return n
50 | }
51 |
52 | func bar() int {
53 | return 4
54 | }
55 |
56 | func statementRemoveStructInitialization() (a http.Header, b error) {
57 | var err error
58 |
59 | a, b = http.Header{}, err
60 |
61 | return
62 | }
63 |
64 | func statementRemoveStringArrayMap() map[string][]string {
65 | hash := "ok"
66 | var hdr = make(map[string][]string)
67 |
68 | hdr["Hash"] = []string{hash}
69 |
70 | return hdr
71 | }
72 |
--------------------------------------------------------------------------------
/testdata/statement/remove.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package example
4 |
5 | import (
6 | "fmt"
7 | "net/http"
8 | )
9 |
10 | func foo() int {
11 | n := 1
12 |
13 | for i := 0; i < 3; i++ {
14 | if i == 0 {
15 | n++
16 | } else if i == 1 {
17 | n += 2
18 | } else {
19 | n += 3
20 | }
21 |
22 | n++
23 | }
24 |
25 | if n < 0 {
26 | n = 0
27 | }
28 |
29 | n++
30 |
31 | n += bar()
32 |
33 | bar()
34 | bar()
35 |
36 | switch {
37 | case n < 20:
38 | n++
39 | case n > 20:
40 | n--
41 | default:
42 | n = 0
43 | fmt.Println(n)
44 | func() {}()
45 | }
46 |
47 | var x = 0
48 | x++
49 |
50 | return n
51 | }
52 |
53 | func bar() int {
54 | return 4
55 | }
56 |
57 | func statementRemoveStructInitialization() (a http.Header, b error) {
58 | var err error
59 |
60 | a, b = http.Header{}, err
61 |
62 | return
63 | }
64 |
65 | func statementRemoveStringArrayMap() map[string][]string {
66 | hash := "ok"
67 | var hdr = make(map[string][]string)
68 |
69 | hdr["Hash"] = []string{hash}
70 |
71 | return hdr
72 | }
73 |
--------------------------------------------------------------------------------
/testdata/statement/remove.go.0.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package example
4 |
5 | import (
6 | "fmt"
7 | "net/http"
8 | )
9 |
10 | func foo() int {
11 | n := 1
12 |
13 | for i := 0; i < 3; i++ {
14 | if i == 0 {
15 | n++
16 | } else if i == 1 {
17 | n += 2
18 | } else {
19 | n += 3
20 | }
21 |
22 | n++
23 | }
24 |
25 | if n < 0 {
26 | n = 0
27 | }
28 | _ = n
29 |
30 | n += bar()
31 |
32 | bar()
33 | bar()
34 |
35 | switch {
36 | case n < 20:
37 | n++
38 | case n > 20:
39 | n--
40 | default:
41 | n = 0
42 | fmt.Println(n)
43 | func() {}()
44 | }
45 |
46 | var x = 0
47 | x++
48 |
49 | return n
50 | }
51 |
52 | func bar() int {
53 | return 4
54 | }
55 |
56 | func statementRemoveStructInitialization() (a http.Header, b error) {
57 | var err error
58 |
59 | a, b = http.Header{}, err
60 |
61 | return
62 | }
63 |
64 | func statementRemoveStringArrayMap() map[string][]string {
65 | hash := "ok"
66 | var hdr = make(map[string][]string)
67 |
68 | hdr["Hash"] = []string{hash}
69 |
70 | return hdr
71 | }
72 |
--------------------------------------------------------------------------------
/testdata/statement/remove.go.4.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package example
4 |
5 | import (
6 | "fmt"
7 | "net/http"
8 | )
9 |
10 | func foo() int {
11 | n := 1
12 |
13 | for i := 0; i < 3; i++ {
14 | if i == 0 {
15 | n++
16 | } else if i == 1 {
17 | n += 2
18 | } else {
19 | n += 3
20 | }
21 |
22 | n++
23 | }
24 |
25 | if n < 0 {
26 | n = 0
27 | }
28 |
29 | n++
30 |
31 | n += bar()
32 |
33 | bar()
34 | bar()
35 |
36 | switch {
37 | case n < 20:
38 | n++
39 | case n > 20:
40 | n--
41 | default:
42 | n = 0
43 | fmt.Println(n)
44 | func() {}()
45 | }
46 |
47 | var x = 0
48 | _ = x
49 |
50 | return n
51 | }
52 |
53 | func bar() int {
54 | return 4
55 | }
56 |
57 | func statementRemoveStructInitialization() (a http.Header, b error) {
58 | var err error
59 |
60 | a, b = http.Header{}, err
61 |
62 | return
63 | }
64 |
65 | func statementRemoveStringArrayMap() map[string][]string {
66 | hash := "ok"
67 | var hdr = make(map[string][]string)
68 |
69 | hdr["Hash"] = []string{hash}
70 |
71 | return hdr
72 | }
73 |
--------------------------------------------------------------------------------
/testdata/statement/remove.go.5.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package example
4 |
5 | import (
6 | "fmt"
7 | "net/http"
8 | )
9 |
10 | func foo() int {
11 | n := 1
12 |
13 | for i := 0; i < 3; i++ {
14 | if i == 0 {
15 | n++
16 | } else if i == 1 {
17 | n += 2
18 | } else {
19 | n += 3
20 | }
21 | _ = n
22 |
23 | }
24 |
25 | if n < 0 {
26 | n = 0
27 | }
28 |
29 | n++
30 |
31 | n += bar()
32 |
33 | bar()
34 | bar()
35 |
36 | switch {
37 | case n < 20:
38 | n++
39 | case n > 20:
40 | n--
41 | default:
42 | n = 0
43 | fmt.Println(n)
44 | func() {}()
45 | }
46 |
47 | var x = 0
48 | x++
49 |
50 | return n
51 | }
52 |
53 | func bar() int {
54 | return 4
55 | }
56 |
57 | func statementRemoveStructInitialization() (a http.Header, b error) {
58 | var err error
59 |
60 | a, b = http.Header{}, err
61 |
62 | return
63 | }
64 |
65 | func statementRemoveStringArrayMap() map[string][]string {
66 | hash := "ok"
67 | var hdr = make(map[string][]string)
68 |
69 | hdr["Hash"] = []string{hash}
70 |
71 | return hdr
72 | }
73 |
--------------------------------------------------------------------------------
/testdata/statement/remove.go.12.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package example
4 |
5 | import (
6 | "fmt"
7 | "net/http"
8 | )
9 |
10 | func foo() int {
11 | n := 1
12 |
13 | for i := 0; i < 3; i++ {
14 | if i == 0 {
15 | n++
16 | } else if i == 1 {
17 | n += 2
18 | } else {
19 | n += 3
20 | }
21 |
22 | n++
23 | }
24 |
25 | if n < 0 {
26 | n = 0
27 | }
28 |
29 | n++
30 |
31 | n += bar()
32 |
33 | bar()
34 | bar()
35 |
36 | switch {
37 | case n < 20:
38 | n++
39 | case n > 20:
40 | n--
41 | default:
42 | _ = n
43 |
44 | fmt.Println(n)
45 | func() {}()
46 | }
47 |
48 | var x = 0
49 | x++
50 |
51 | return n
52 | }
53 |
54 | func bar() int {
55 | return 4
56 | }
57 |
58 | func statementRemoveStructInitialization() (a http.Header, b error) {
59 | var err error
60 |
61 | a, b = http.Header{}, err
62 |
63 | return
64 | }
65 |
66 | func statementRemoveStringArrayMap() map[string][]string {
67 | hash := "ok"
68 | var hdr = make(map[string][]string)
69 |
70 | hdr["Hash"] = []string{hash}
71 |
72 | return hdr
73 | }
74 |
--------------------------------------------------------------------------------
/testdata/statement/remove.go.13.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package example
4 |
5 | import (
6 | "fmt"
7 | "net/http"
8 | )
9 |
10 | func foo() int {
11 | n := 1
12 |
13 | for i := 0; i < 3; i++ {
14 | if i == 0 {
15 | n++
16 | } else if i == 1 {
17 | n += 2
18 | } else {
19 | n += 3
20 | }
21 |
22 | n++
23 | }
24 |
25 | if n < 0 {
26 | n = 0
27 | }
28 |
29 | n++
30 |
31 | n += bar()
32 |
33 | bar()
34 | bar()
35 |
36 | switch {
37 | case n < 20:
38 | n++
39 | case n > 20:
40 | n--
41 | default:
42 | n = 0
43 | _, _ = fmt.Println, n
44 | func() {}()
45 | }
46 |
47 | var x = 0
48 | x++
49 |
50 | return n
51 | }
52 |
53 | func bar() int {
54 | return 4
55 | }
56 |
57 | func statementRemoveStructInitialization() (a http.Header, b error) {
58 | var err error
59 |
60 | a, b = http.Header{}, err
61 |
62 | return
63 | }
64 |
65 | func statementRemoveStringArrayMap() map[string][]string {
66 | hash := "ok"
67 | var hdr = make(map[string][]string)
68 |
69 | hdr["Hash"] = []string{hash}
70 |
71 | return hdr
72 | }
73 |
--------------------------------------------------------------------------------
/testdata/statement/remove.go.15.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package example
4 |
5 | import (
6 | "fmt"
7 | "net/http"
8 | )
9 |
10 | func foo() int {
11 | n := 1
12 |
13 | for i := 0; i < 3; i++ {
14 | if i == 0 {
15 | n++
16 | } else if i == 1 {
17 | n += 2
18 | } else {
19 | n += 3
20 | }
21 |
22 | n++
23 | }
24 |
25 | if n < 0 {
26 | n = 0
27 | }
28 |
29 | n++
30 |
31 | n += bar()
32 |
33 | bar()
34 | bar()
35 |
36 | switch {
37 | case n < 20:
38 | n++
39 | case n > 20:
40 | n--
41 | default:
42 | n = 0
43 | fmt.Println(n)
44 | func() {}()
45 | }
46 |
47 | var x = 0
48 | x++
49 |
50 | return n
51 | }
52 |
53 | func bar() int {
54 | return 4
55 | }
56 |
57 | func statementRemoveStructInitialization() (a http.Header, b error) {
58 | var err error
59 | _, _, _, _ = a, b, http.Header{}, err
60 |
61 | return
62 | }
63 |
64 | func statementRemoveStringArrayMap() map[string][]string {
65 | hash := "ok"
66 | var hdr = make(map[string][]string)
67 |
68 | hdr["Hash"] = []string{hash}
69 |
70 | return hdr
71 | }
72 |
--------------------------------------------------------------------------------
/testdata/statement/remove.go.6.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package example
4 |
5 | import (
6 | "fmt"
7 | "net/http"
8 | )
9 |
10 | func foo() int {
11 | n := 1
12 |
13 | for i := 0; i < 3; i++ {
14 | if i == 0 {
15 | _ = n
16 |
17 | } else if i == 1 {
18 | n += 2
19 | } else {
20 | n += 3
21 | }
22 |
23 | n++
24 | }
25 |
26 | if n < 0 {
27 | n = 0
28 | }
29 |
30 | n++
31 |
32 | n += bar()
33 |
34 | bar()
35 | bar()
36 |
37 | switch {
38 | case n < 20:
39 | n++
40 | case n > 20:
41 | n--
42 | default:
43 | n = 0
44 | fmt.Println(n)
45 | func() {}()
46 | }
47 |
48 | var x = 0
49 | x++
50 |
51 | return n
52 | }
53 |
54 | func bar() int {
55 | return 4
56 | }
57 |
58 | func statementRemoveStructInitialization() (a http.Header, b error) {
59 | var err error
60 |
61 | a, b = http.Header{}, err
62 |
63 | return
64 | }
65 |
66 | func statementRemoveStringArrayMap() map[string][]string {
67 | hash := "ok"
68 | var hdr = make(map[string][]string)
69 |
70 | hdr["Hash"] = []string{hash}
71 |
72 | return hdr
73 | }
74 |
--------------------------------------------------------------------------------
/testdata/statement/remove.go.7.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package example
4 |
5 | import (
6 | "fmt"
7 | "net/http"
8 | )
9 |
10 | func foo() int {
11 | n := 1
12 |
13 | for i := 0; i < 3; i++ {
14 | if i == 0 {
15 | n++
16 | } else if i == 1 {
17 | _ = n
18 |
19 | } else {
20 | n += 3
21 | }
22 |
23 | n++
24 | }
25 |
26 | if n < 0 {
27 | n = 0
28 | }
29 |
30 | n++
31 |
32 | n += bar()
33 |
34 | bar()
35 | bar()
36 |
37 | switch {
38 | case n < 20:
39 | n++
40 | case n > 20:
41 | n--
42 | default:
43 | n = 0
44 | fmt.Println(n)
45 | func() {}()
46 | }
47 |
48 | var x = 0
49 | x++
50 |
51 | return n
52 | }
53 |
54 | func bar() int {
55 | return 4
56 | }
57 |
58 | func statementRemoveStructInitialization() (a http.Header, b error) {
59 | var err error
60 |
61 | a, b = http.Header{}, err
62 |
63 | return
64 | }
65 |
66 | func statementRemoveStringArrayMap() map[string][]string {
67 | hash := "ok"
68 | var hdr = make(map[string][]string)
69 |
70 | hdr["Hash"] = []string{hash}
71 |
72 | return hdr
73 | }
74 |
--------------------------------------------------------------------------------
/testdata/statement/remove.go.8.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package example
4 |
5 | import (
6 | "fmt"
7 | "net/http"
8 | )
9 |
10 | func foo() int {
11 | n := 1
12 |
13 | for i := 0; i < 3; i++ {
14 | if i == 0 {
15 | n++
16 | } else if i == 1 {
17 | n += 2
18 | } else {
19 | _ = n
20 |
21 | }
22 |
23 | n++
24 | }
25 |
26 | if n < 0 {
27 | n = 0
28 | }
29 |
30 | n++
31 |
32 | n += bar()
33 |
34 | bar()
35 | bar()
36 |
37 | switch {
38 | case n < 20:
39 | n++
40 | case n > 20:
41 | n--
42 | default:
43 | n = 0
44 | fmt.Println(n)
45 | func() {}()
46 | }
47 |
48 | var x = 0
49 | x++
50 |
51 | return n
52 | }
53 |
54 | func bar() int {
55 | return 4
56 | }
57 |
58 | func statementRemoveStructInitialization() (a http.Header, b error) {
59 | var err error
60 |
61 | a, b = http.Header{}, err
62 |
63 | return
64 | }
65 |
66 | func statementRemoveStringArrayMap() map[string][]string {
67 | hash := "ok"
68 | var hdr = make(map[string][]string)
69 |
70 | hdr["Hash"] = []string{hash}
71 |
72 | return hdr
73 | }
74 |
--------------------------------------------------------------------------------
/testdata/statement/remove.go.9.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package example
4 |
5 | import (
6 | "fmt"
7 | "net/http"
8 | )
9 |
10 | func foo() int {
11 | n := 1
12 |
13 | for i := 0; i < 3; i++ {
14 | if i == 0 {
15 | n++
16 | } else if i == 1 {
17 | n += 2
18 | } else {
19 | n += 3
20 | }
21 |
22 | n++
23 | }
24 |
25 | if n < 0 {
26 | _ = n
27 |
28 | }
29 |
30 | n++
31 |
32 | n += bar()
33 |
34 | bar()
35 | bar()
36 |
37 | switch {
38 | case n < 20:
39 | n++
40 | case n > 20:
41 | n--
42 | default:
43 | n = 0
44 | fmt.Println(n)
45 | func() {}()
46 | }
47 |
48 | var x = 0
49 | x++
50 |
51 | return n
52 | }
53 |
54 | func bar() int {
55 | return 4
56 | }
57 |
58 | func statementRemoveStructInitialization() (a http.Header, b error) {
59 | var err error
60 |
61 | a, b = http.Header{}, err
62 |
63 | return
64 | }
65 |
66 | func statementRemoveStringArrayMap() map[string][]string {
67 | hash := "ok"
68 | var hdr = make(map[string][]string)
69 |
70 | hdr["Hash"] = []string{hash}
71 |
72 | return hdr
73 | }
74 |
--------------------------------------------------------------------------------
/testdata/statement/remove.go.10.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package example
4 |
5 | import (
6 | "fmt"
7 | "net/http"
8 | )
9 |
10 | func foo() int {
11 | n := 1
12 |
13 | for i := 0; i < 3; i++ {
14 | if i == 0 {
15 | n++
16 | } else if i == 1 {
17 | n += 2
18 | } else {
19 | n += 3
20 | }
21 |
22 | n++
23 | }
24 |
25 | if n < 0 {
26 | n = 0
27 | }
28 |
29 | n++
30 |
31 | n += bar()
32 |
33 | bar()
34 | bar()
35 |
36 | switch {
37 | case n < 20:
38 | _ = n
39 |
40 | case n > 20:
41 | n--
42 | default:
43 | n = 0
44 | fmt.Println(n)
45 | func() {}()
46 | }
47 |
48 | var x = 0
49 | x++
50 |
51 | return n
52 | }
53 |
54 | func bar() int {
55 | return 4
56 | }
57 |
58 | func statementRemoveStructInitialization() (a http.Header, b error) {
59 | var err error
60 |
61 | a, b = http.Header{}, err
62 |
63 | return
64 | }
65 |
66 | func statementRemoveStringArrayMap() map[string][]string {
67 | hash := "ok"
68 | var hdr = make(map[string][]string)
69 |
70 | hdr["Hash"] = []string{hash}
71 |
72 | return hdr
73 | }
74 |
--------------------------------------------------------------------------------
/testdata/statement/remove.go.11.go:
--------------------------------------------------------------------------------
1 | // +build test
2 |
3 | package example
4 |
5 | import (
6 | "fmt"
7 | "net/http"
8 | )
9 |
10 | func foo() int {
11 | n := 1
12 |
13 | for i := 0; i < 3; i++ {
14 | if i == 0 {
15 | n++
16 | } else if i == 1 {
17 | n += 2
18 | } else {
19 | n += 3
20 | }
21 |
22 | n++
23 | }
24 |
25 | if n < 0 {
26 | n = 0
27 | }
28 |
29 | n++
30 |
31 | n += bar()
32 |
33 | bar()
34 | bar()
35 |
36 | switch {
37 | case n < 20:
38 | n++
39 | case n > 20:
40 | _ = n
41 |
42 | default:
43 | n = 0
44 | fmt.Println(n)
45 | func() {}()
46 | }
47 |
48 | var x = 0
49 | x++
50 |
51 | return n
52 | }
53 |
54 | func bar() int {
55 | return 4
56 | }
57 |
58 | func statementRemoveStructInitialization() (a http.Header, b error) {
59 | var err error
60 |
61 | a, b = http.Header{}, err
62 |
63 | return
64 | }
65 |
66 | func statementRemoveStringArrayMap() map[string][]string {
67 | hash := "ok"
68 | var hdr = make(map[string][]string)
69 |
70 | hdr["Hash"] = []string{hash}
71 |
72 | return hdr
73 | }
74 |
--------------------------------------------------------------------------------
/scripts/lint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | if [ -z ${PKG+x} ]; then echo "PKG is not set"; exit 1; fi
4 | if [ -z ${ROOT_DIR+x} ]; then echo "ROOT_DIR is not set"; exit 1; fi
5 |
6 | echo "gofmt:"
7 | OUT=$(gofmt -l -s $ROOT_DIR 2>&1 | grep --invert-match -E "(/(example|vendor))")
8 | if [ -n "$OUT" ]; then echo "$OUT"; PROBLEM=1; fi
9 |
10 | echo "errcheck:"
11 | OUT=$(errcheck $PKG/... 2>&1 | grep --invert-match -E "(/(example|vendor))")
12 | if [ -n "$OUT" ]; then echo "$OUT"; PROBLEM=1; fi
13 |
14 | echo "go vet:"
15 | OUT=$(go vet -all=true $ROOT_DIR 2>&1 | grep --invert-match -E "(Checking file|\%p of wrong type|can't check non-constant format|/example|/vendor)")
16 | if [ -n "$OUT" ]; then echo "$OUT"; PROBLEM=1; fi
17 |
18 | echo "golint:"
19 | OUT=$(golint $PKG/... 2>&1 | grep --invert-match -E "(/(example|vendor))")
20 | if [ -n "$OUT" ]; then echo "$OUT"; PROBLEM=1; fi
21 |
22 | echo "staticcheck:"
23 | OUT=$(staticcheck $PKG/... 2>&1 | grep --invert-match -E "((example|vendor)/)")
24 | if [ -n "$OUT" ]; then echo "$OUT"; PROBLEM=1; fi
25 |
26 | if [ -n "$PROBLEM" ]; then exit 1; fi
27 |
--------------------------------------------------------------------------------
/mutator/expression/remove.go:
--------------------------------------------------------------------------------
1 | package expression
2 |
3 | import (
4 | "go/ast"
5 | "go/token"
6 | "go/types"
7 |
8 | "github.com/zimmski/go-mutesting/mutator"
9 | )
10 |
11 | func init() {
12 | mutator.Register("expression/remove", MutatorRemoveTerm)
13 | }
14 |
15 | // MutatorRemoveTerm implements a mutator to remove expression terms.
16 | func MutatorRemoveTerm(pkg *types.Package, info *types.Info, node ast.Node) []mutator.Mutation {
17 | n, ok := node.(*ast.BinaryExpr)
18 | if !ok {
19 | return nil
20 | }
21 | if n.Op != token.LAND && n.Op != token.LOR {
22 | return nil
23 | }
24 |
25 | var r *ast.Ident
26 |
27 | switch n.Op {
28 | case token.LAND:
29 | r = ast.NewIdent("true")
30 | case token.LOR:
31 | r = ast.NewIdent("false")
32 | }
33 |
34 | x := n.X
35 | y := n.Y
36 |
37 | return []mutator.Mutation{
38 | {
39 | Change: func() {
40 | n.X = r
41 | },
42 | Reset: func() {
43 | n.X = x
44 | },
45 | },
46 | {
47 | Change: func() {
48 | n.Y = r
49 | },
50 | Reset: func() {
51 | n.Y = y
52 | },
53 | },
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Markus Zimmermann
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/mutator/statement/remove.go:
--------------------------------------------------------------------------------
1 | package statement
2 |
3 | import (
4 | "go/ast"
5 | "go/token"
6 | "go/types"
7 |
8 | "github.com/zimmski/go-mutesting/astutil"
9 | "github.com/zimmski/go-mutesting/mutator"
10 | )
11 |
12 | func init() {
13 | mutator.Register("statement/remove", MutatorRemoveStatement)
14 | }
15 |
16 | func checkRemoveStatement(node ast.Stmt) bool {
17 | switch n := node.(type) {
18 | case *ast.AssignStmt:
19 | if n.Tok != token.DEFINE {
20 | return true
21 | }
22 | case *ast.ExprStmt, *ast.IncDecStmt:
23 | return true
24 | }
25 |
26 | return false
27 | }
28 |
29 | // MutatorRemoveStatement implements a mutator to remove statements.
30 | func MutatorRemoveStatement(pkg *types.Package, info *types.Info, node ast.Node) []mutator.Mutation {
31 | var l []ast.Stmt
32 |
33 | switch n := node.(type) {
34 | case *ast.BlockStmt:
35 | l = n.List
36 | case *ast.CaseClause:
37 | l = n.Body
38 | }
39 |
40 | var mutations []mutator.Mutation
41 |
42 | for i, ni := range l {
43 | if checkRemoveStatement(ni) {
44 | li := i
45 | old := l[li]
46 |
47 | mutations = append(mutations, mutator.Mutation{
48 | Change: func() {
49 | l[li] = astutil.CreateNoopOfStatement(pkg, info, old)
50 | },
51 | Reset: func() {
52 | l[li] = old
53 | },
54 | })
55 | }
56 | }
57 |
58 | return mutations
59 | }
60 |
--------------------------------------------------------------------------------
/mutator/mutator_test.go:
--------------------------------------------------------------------------------
1 | package mutator
2 |
3 | import (
4 | "go/ast"
5 | "go/types"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func mockMutator(pkg *types.Package, info *types.Info, node ast.Node) []Mutation {
12 | // Do nothing
13 |
14 | return nil
15 | }
16 |
17 | func TestMockMutator(t *testing.T) {
18 | // Mock is not registered
19 | for _, name := range List() {
20 | if name == "mock" {
21 | assert.Fail(t, "mock should not be in the mutator list yet")
22 | }
23 | }
24 |
25 | m, err := New("mock")
26 | assert.Nil(t, m)
27 | assert.NotNil(t, err)
28 |
29 | // Register mock
30 | Register("mock", mockMutator)
31 |
32 | // Mock is registered
33 | found := false
34 | for _, name := range List() {
35 | if name == "mock" {
36 | found = true
37 |
38 | break
39 | }
40 | }
41 | assert.True(t, found)
42 |
43 | m, err = New("mock")
44 | assert.NotNil(t, m)
45 | assert.Nil(t, err)
46 |
47 | // Register mock a second time
48 | caught := false
49 | func() {
50 | defer func() {
51 | if r := recover(); r != nil {
52 | caught = true
53 | }
54 | }()
55 |
56 | Register("mock", mockMutator)
57 | }()
58 | assert.True(t, caught)
59 |
60 | // Register nil function
61 | caught = false
62 | func() {
63 | defer func() {
64 | if r := recover(); r != nil {
65 | caught = true
66 | }
67 | }()
68 |
69 | Register("mockachino", nil)
70 | }()
71 | assert.True(t, caught)
72 | }
73 |
--------------------------------------------------------------------------------
/mutator/mutator.go:
--------------------------------------------------------------------------------
1 | package mutator
2 |
3 | import (
4 | "fmt"
5 | "go/ast"
6 | "go/types"
7 | "sort"
8 | )
9 |
10 | // Mutator defines a mutator for mutation testing by returning a list of possible mutations for the given node.
11 | type Mutator func(pkg *types.Package, info *types.Info, node ast.Node) []Mutation
12 |
13 | var mutatorLookup = make(map[string]Mutator)
14 |
15 | // New returns a new mutator instance given the registered name of the mutator.
16 | // The error return argument is not nil, if the name does not exist in the registered mutator list.
17 | func New(name string) (Mutator, error) {
18 | mutator, ok := mutatorLookup[name]
19 | if !ok {
20 | return nil, fmt.Errorf("unknown mutator %q", name)
21 | }
22 |
23 | return mutator, nil
24 | }
25 |
26 | // List returns a list of all registered mutator names.
27 | func List() []string {
28 | keyMutatorLookup := make([]string, 0, len(mutatorLookup))
29 |
30 | for key := range mutatorLookup {
31 | keyMutatorLookup = append(keyMutatorLookup, key)
32 | }
33 |
34 | sort.Strings(keyMutatorLookup)
35 |
36 | return keyMutatorLookup
37 | }
38 |
39 | // Register registers a mutator instance function with the given name.
40 | func Register(name string, mutator Mutator) {
41 | if mutator == nil {
42 | panic("mutator function is nil")
43 | }
44 |
45 | if _, ok := mutatorLookup[name]; ok {
46 | panic(fmt.Sprintf("mutator %q already registered", name))
47 | }
48 |
49 | mutatorLookup[name] = mutator
50 | }
51 |
--------------------------------------------------------------------------------
/scripts/exec/test-current-directory.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # This exec script implements
4 | # - the replacement of the original file with the mutation,
5 | # - the execution of all tests originating from the current directory,
6 | # - and the reporting if the mutation was killed.
7 |
8 | if [ -z ${MUTATE_CHANGED+x} ]; then echo "MUTATE_CHANGED is not set"; exit 1; fi
9 | if [ -z ${MUTATE_ORIGINAL+x} ]; then echo "MUTATE_ORIGINAL is not set"; exit 1; fi
10 | if [ -z ${MUTATE_PACKAGE+x} ]; then echo "MUTATE_PACKAGE is not set"; exit 1; fi
11 |
12 | function clean_up {
13 | if [ -f $MUTATE_ORIGINAL.tmp ];
14 | then
15 | mv $MUTATE_ORIGINAL.tmp $MUTATE_ORIGINAL
16 | fi
17 | }
18 |
19 | function sig_handler {
20 | clean_up
21 |
22 | exit $GOMUTESTING_RESULT
23 | }
24 | trap sig_handler SIGHUP SIGINT SIGTERM
25 |
26 | export GOMUTESTING_DIFF=$(diff -u $MUTATE_ORIGINAL $MUTATE_CHANGED)
27 |
28 | mv $MUTATE_ORIGINAL $MUTATE_ORIGINAL.tmp
29 | cp $MUTATE_CHANGED $MUTATE_ORIGINAL
30 |
31 | export MUTATE_TIMEOUT=${MUTATE_TIMEOUT:-10}
32 |
33 | if [ -n "$TEST_RECURSIVE" ]; then
34 | TEST_RECURSIVE="/..."
35 | fi
36 |
37 | GOMUTESTING_TEST=$(go test -timeout $(printf '%ds' $MUTATE_TIMEOUT) .$TEST_RECURSIVE 2>&1)
38 | export GOMUTESTING_RESULT=$?
39 |
40 | if [ "$MUTATE_DEBUG" = true ] ; then
41 | echo "$GOMUTESTING_TEST"
42 | fi
43 |
44 | clean_up
45 |
46 | case $GOMUTESTING_RESULT in
47 | 0) # tests passed -> FAIL
48 | echo "$GOMUTESTING_DIFF"
49 |
50 | exit 1
51 | ;;
52 | 1) # tests failed -> PASS
53 | if [ "$MUTATE_DEBUG" = true ] ; then
54 | echo "$GOMUTESTING_DIFF"
55 | fi
56 |
57 | exit 0
58 | ;;
59 | 2) # did not compile -> SKIP
60 | if [ "$MUTATE_VERBOSE" = true ] ; then
61 | echo "Mutation did not compile"
62 | fi
63 |
64 | if [ "$MUTATE_DEBUG" = true ] ; then
65 | echo "$GOMUTESTING_DIFF"
66 | fi
67 |
68 | exit 2
69 | ;;
70 | *) # Unkown exit code -> SKIP
71 | echo "Unknown exit code"
72 | echo "$GOMUTESTING_DIFF"
73 |
74 | exit $GOMUTESTING_RESULT
75 | ;;
76 | esac
77 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: all clean clean-coverage generate install install-dependencies install-tools lint test test-verbose test-verbose-with-coverage
2 |
3 | export ARGS := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS))
4 | export PKG := github.com/zimmski/go-mutesting
5 | export ROOT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
6 |
7 | export TEST_TIMEOUT_IN_SECONDS := 240
8 |
9 | $(eval $(ARGS):;@:) # turn arguments into do-nothing targets
10 | export ARGS
11 |
12 | ifdef ARGS
13 | PKG_TEST := $(ARGS)
14 | else
15 | PKG_TEST := $(PKG)/...
16 | endif
17 |
18 | all: install-dependencies install-tools install lint test
19 | .PHONY: all
20 |
21 | clean:
22 | go clean -i $(PKG)/...
23 | go clean -i -race $(PKG)/...
24 | .PHONY: clean
25 |
26 | clean-coverage:
27 | find $(ROOT_DIR) | grep .coverprofile | xargs rm
28 | .PHONY: clean-coverage
29 |
30 | generate: clean
31 | go generate $(PKG)/...
32 | .PHONY: generate
33 |
34 | install:
35 | go install -v $(PKG)/...
36 | .PHONY: install
37 |
38 | install-dependencies:
39 | go mod vendor
40 | go test -i -v $(PKG)/...
41 | .PHONY: install-dependencies
42 |
43 | install-tools:
44 | # generation
45 | go get golang.org/x/tools/cmd/stringer
46 |
47 | # linting
48 | go get golang.org/x/lint/golint/...
49 | go get github.com/kisielk/errcheck/...
50 | go get honnef.co/go/tools/...
51 |
52 | # code coverage
53 | go get golang.org/x/tools/cmd/cover
54 | go get github.com/onsi/ginkgo/ginkgo/...
55 | go get github.com/modocache/gover/...
56 | go get github.com/mattn/goveralls/...
57 | .PHONY: install-tools
58 |
59 | lint:
60 | $(ROOT_DIR)/scripts/lint.sh
61 | .PHONY: lint
62 |
63 | test:
64 | go test -race -test.timeout "$(TEST_TIMEOUT_IN_SECONDS)s" $(PKG_TEST)
65 | .PHONY: test
66 |
67 | test-verbose:
68 | go test -race -test.timeout "$(TEST_TIMEOUT_IN_SECONDS)s" -v $(PKG_TEST)
69 | .PHONY: test-verbose
70 |
71 | test-verbose-with-coverage:
72 | ginkgo -r -v -cover -race -skipPackage="testdata"
73 | .PHONY: test-verbose-with-coverage
74 |
--------------------------------------------------------------------------------
/scripts/exec/test-mutated-package.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # This exec script implements
4 | # - the replacement of the original file with the mutation,
5 | # - the execution of all tests originating from the package of the mutated file,
6 | # - and the reporting if the mutation was killed.
7 |
8 | if [ -z ${MUTATE_CHANGED+x} ]; then echo "MUTATE_CHANGED is not set"; exit 1; fi
9 | if [ -z ${MUTATE_ORIGINAL+x} ]; then echo "MUTATE_ORIGINAL is not set"; exit 1; fi
10 | if [ -z ${MUTATE_PACKAGE+x} ]; then echo "MUTATE_PACKAGE is not set"; exit 1; fi
11 |
12 | function clean_up {
13 | if [ -f $MUTATE_ORIGINAL.tmp ];
14 | then
15 | mv $MUTATE_ORIGINAL.tmp $MUTATE_ORIGINAL
16 | fi
17 | }
18 |
19 | function sig_handler {
20 | clean_up
21 |
22 | exit $GOMUTESTING_RESULT
23 | }
24 | trap sig_handler SIGHUP SIGINT SIGTERM
25 |
26 | export GOMUTESTING_DIFF=$(diff -u $MUTATE_ORIGINAL $MUTATE_CHANGED)
27 |
28 | mv $MUTATE_ORIGINAL $MUTATE_ORIGINAL.tmp
29 | cp $MUTATE_CHANGED $MUTATE_ORIGINAL
30 |
31 | export MUTATE_TIMEOUT=${MUTATE_TIMEOUT:-10}
32 |
33 | if [ -n "$TEST_RECURSIVE" ]; then
34 | TEST_RECURSIVE="/..."
35 | fi
36 |
37 | GOMUTESTING_TEST=$(go test -timeout $(printf '%ds' $MUTATE_TIMEOUT) $MUTATE_PACKAGE$TEST_RECURSIVE 2>&1)
38 | export GOMUTESTING_RESULT=$?
39 |
40 | if [ "$MUTATE_DEBUG" = true ] ; then
41 | echo "$GOMUTESTING_TEST"
42 | fi
43 |
44 | clean_up
45 |
46 | case $GOMUTESTING_RESULT in
47 | 0) # tests passed -> FAIL
48 | echo "$GOMUTESTING_DIFF"
49 |
50 | exit 1
51 | ;;
52 | 1) # tests failed -> PASS
53 | if [ "$MUTATE_DEBUG" = true ] ; then
54 | echo "$GOMUTESTING_DIFF"
55 | fi
56 |
57 | exit 0
58 | ;;
59 | 2) # did not compile -> SKIP
60 | if [ "$MUTATE_VERBOSE" = true ] ; then
61 | echo "Mutation did not compile"
62 | fi
63 |
64 | if [ "$MUTATE_DEBUG" = true ] ; then
65 | echo "$GOMUTESTING_DIFF"
66 | fi
67 |
68 | exit 2
69 | ;;
70 | *) # Unkown exit code -> SKIP
71 | echo "Unknown exit code"
72 | echo "$GOMUTESTING_DIFF"
73 |
74 | exit $GOMUTESTING_RESULT
75 | ;;
76 | esac
77 |
--------------------------------------------------------------------------------
/test/mutator.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "go/printer"
7 | "io/ioutil"
8 | "testing"
9 |
10 | "github.com/stretchr/testify/assert"
11 |
12 | "github.com/zimmski/go-mutesting"
13 | "github.com/zimmski/go-mutesting/mutator"
14 | )
15 |
16 | // Mutator tests a mutator.
17 | // 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.
18 | func Mutator(t *testing.T, m mutator.Mutator, testFile string, count int) {
19 | // Test if mutator is not nil
20 | assert.NotNil(t, m)
21 |
22 | // Read the origianl source code
23 | data, err := ioutil.ReadFile(testFile)
24 | assert.Nil(t, err)
25 |
26 | // Parse and type-check the original source code
27 | src, fset, pkg, info, err := mutesting.ParseAndTypeCheckFile(testFile, `-tags=test`)
28 | assert.Nil(t, err)
29 |
30 | // Mutate a non relevant node
31 | assert.Nil(t, m(pkg, info, src))
32 |
33 | // Count the actual mutations
34 | n := mutesting.CountWalk(pkg, info, src, m)
35 | assert.Equal(t, count, n)
36 |
37 | // Mutate all relevant nodes -> test whole mutation process
38 | changed := mutesting.MutateWalk(pkg, info, src, m)
39 |
40 | for i := 0; i < count; i++ {
41 | assert.True(t, <-changed)
42 |
43 | buf := new(bytes.Buffer)
44 | err = printer.Fprint(buf, fset, src)
45 | assert.Nil(t, err)
46 |
47 | changedFilename := fmt.Sprintf("%s.%d.go", testFile, i)
48 | changedFile, err := ioutil.ReadFile(changedFilename)
49 | assert.Nil(t, err)
50 |
51 | if !assert.Equal(t, string(changedFile), buf.String(), fmt.Sprintf("For change file %q", changedFilename)) {
52 | err = ioutil.WriteFile(fmt.Sprintf("%s.%d.go.new", testFile, i), buf.Bytes(), 0644)
53 | assert.Nil(t, err)
54 | }
55 |
56 | changed <- true
57 |
58 | assert.True(t, <-changed)
59 |
60 | buf = new(bytes.Buffer)
61 | err = printer.Fprint(buf, fset, src)
62 | assert.Nil(t, err)
63 |
64 | assert.Equal(t, string(data), buf.String())
65 |
66 | changed <- true
67 | }
68 |
69 | _, ok := <-changed
70 | assert.False(t, ok)
71 | }
72 |
--------------------------------------------------------------------------------
/cmd/go-mutesting/main_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "io"
6 | "os"
7 | "testing"
8 |
9 | "github.com/stretchr/testify/assert"
10 | )
11 |
12 | func TestMain(t *testing.T) {
13 | testMain(
14 | t,
15 | "../../example",
16 | []string{"--debug", "--exec-timeout", "1"},
17 | returnOk,
18 | "The mutation score is 0.450000 (9 passed, 11 failed, 8 duplicated, 0 skipped, total is 20)",
19 | )
20 | }
21 |
22 | func TestMainRecursive(t *testing.T) {
23 | testMain(
24 | t,
25 | "../../example",
26 | []string{"--debug", "--exec-timeout", "1", "./..."},
27 | returnOk,
28 | "The mutation score is 0.476190 (10 passed, 11 failed, 8 duplicated, 0 skipped, total is 21)",
29 | )
30 | }
31 |
32 | func TestMainFromOtherDirectory(t *testing.T) {
33 | testMain(
34 | t,
35 | "../..",
36 | []string{"--debug", "--exec-timeout", "1", "github.com/zimmski/go-mutesting/example"},
37 | returnOk,
38 | "The mutation score is 0.450000 (9 passed, 11 failed, 8 duplicated, 0 skipped, total is 20)",
39 | )
40 | }
41 |
42 | func TestMainMatch(t *testing.T) {
43 | testMain(
44 | t,
45 | "../../example",
46 | []string{"--debug", "--exec", "../scripts/exec/test-mutated-package.sh", "--exec-timeout", "1", "--match", "baz", "./..."},
47 | returnOk,
48 | "The mutation score is 0.500000 (1 passed, 1 failed, 0 duplicated, 0 skipped, total is 2)",
49 | )
50 | }
51 |
52 | func testMain(t *testing.T, root string, exec []string, expectedExitCode int, contains string) {
53 | saveStderr := os.Stderr
54 | saveStdout := os.Stdout
55 | saveCwd, err := os.Getwd()
56 | assert.Nil(t, err)
57 |
58 | r, w, err := os.Pipe()
59 | assert.Nil(t, err)
60 |
61 | os.Stderr = w
62 | os.Stdout = w
63 | assert.Nil(t, os.Chdir(root))
64 |
65 | bufChannel := make(chan string)
66 |
67 | go func() {
68 | buf := new(bytes.Buffer)
69 | _, err = io.Copy(buf, r)
70 | assert.Nil(t, err)
71 | assert.Nil(t, r.Close())
72 |
73 | bufChannel <- buf.String()
74 | }()
75 |
76 | exitCode := mainCmd(exec)
77 |
78 | assert.Nil(t, w.Close())
79 |
80 | os.Stderr = saveStderr
81 | os.Stdout = saveStdout
82 | assert.Nil(t, os.Chdir(saveCwd))
83 |
84 | out := <-bufChannel
85 |
86 | assert.Equal(t, expectedExitCode, exitCode)
87 | assert.Contains(t, out, contains)
88 | }
89 |
--------------------------------------------------------------------------------
/parse.go:
--------------------------------------------------------------------------------
1 | package mutesting
2 |
3 | import (
4 | "fmt"
5 | "go/ast"
6 | "go/build"
7 | "go/parser"
8 | "go/token"
9 | "go/types"
10 | "io/ioutil"
11 | "path/filepath"
12 |
13 | "golang.org/x/tools/go/packages"
14 | )
15 |
16 | // ParseFile parses the content of the given file and returns the corresponding ast.File node and its file set for positional information.
17 | // If a fatal error is encountered the error return argument is not nil.
18 | func ParseFile(file string) (*ast.File, *token.FileSet, error) {
19 | data, err := ioutil.ReadFile(file)
20 | if err != nil {
21 | return nil, nil, err
22 | }
23 |
24 | return ParseSource(data)
25 | }
26 |
27 | // ParseSource parses the given source and returns the corresponding ast.File node and its file set for positional information.
28 | // If a fatal error is encountered the error return argument is not nil.
29 | func ParseSource(data interface{}) (*ast.File, *token.FileSet, error) {
30 | fset := token.NewFileSet()
31 |
32 | src, err := parser.ParseFile(fset, "", data, parser.ParseComments|parser.AllErrors)
33 | if err != nil {
34 | return nil, nil, err
35 | }
36 |
37 | return src, fset, err
38 | }
39 |
40 | // ParseAndTypeCheckFile parses and type-checks the given file, and returns everything interesting about the file.
41 | // If a fatal error is encountered the error return argument is not nil.
42 | func ParseAndTypeCheckFile(file string, flags ...string) (*ast.File, *token.FileSet, *types.Package, *types.Info, error) {
43 | fileAbs, err := filepath.Abs(file)
44 | if err != nil {
45 | return nil, nil, nil, nil, fmt.Errorf("could not absolute the file path of %q: %v", file, err)
46 | }
47 | dir := filepath.Dir(fileAbs)
48 |
49 | buildPkg, err := build.ImportDir(dir, build.FindOnly)
50 | if err != nil {
51 | return nil, nil, nil, nil, fmt.Errorf("could not create build package of %q: %v", file, err)
52 | }
53 |
54 | pkgPath := buildPkg.ImportPath
55 | if buildPkg.ImportPath == "." {
56 | pkgPath = dir
57 | }
58 |
59 | prog, err := packages.Load(&packages.Config{
60 | ParseFile: func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) {
61 | return parser.ParseFile(fset, filename, src, parser.ParseComments|parser.AllErrors)
62 | },
63 | BuildFlags: flags,
64 | Mode: packages.NeedTypes | packages.NeedSyntax | packages.NeedDeps | packages.NeedName | packages.NeedImports | packages.NeedTypesInfo | packages.NeedFiles,
65 | }, pkgPath)
66 | if err != nil {
67 | fmt.Println(err)
68 | return nil, nil, nil, nil, fmt.Errorf("could not load package of file %q: %v", file, err)
69 | }
70 |
71 | pkgInfo := prog[0]
72 |
73 | var src *ast.File
74 | for _, f := range pkgInfo.Syntax {
75 | if pkgInfo.Fset.Position(f.Pos()).Filename == fileAbs {
76 | src = f
77 |
78 | break
79 | }
80 | }
81 |
82 | return src, pkgInfo.Fset, pkgInfo.Types, pkgInfo.TypesInfo, nil
83 | }
84 |
--------------------------------------------------------------------------------
/astutil/query.go:
--------------------------------------------------------------------------------
1 | package astutil
2 |
3 | import (
4 | "go/ast"
5 | "go/token"
6 | "go/types"
7 | )
8 |
9 | // IdentifiersInStatement returns all identifiers with their found in a statement.
10 | func IdentifiersInStatement(pkg *types.Package, info *types.Info, stmt ast.Stmt) []ast.Expr {
11 | w := &identifierWalker{
12 | pkg: pkg,
13 | info: info,
14 | }
15 |
16 | ast.Walk(w, stmt)
17 |
18 | return w.identifiers
19 | }
20 |
21 | type identifierWalker struct {
22 | identifiers []ast.Expr
23 | pkg *types.Package
24 | info *types.Info
25 | }
26 |
27 | func checkForSelectorExpr(node ast.Expr) bool {
28 | switch n := node.(type) {
29 | case *ast.Ident:
30 | return true
31 | case *ast.SelectorExpr:
32 | return checkForSelectorExpr(n.X)
33 | }
34 |
35 | return false
36 | }
37 |
38 | func (w *identifierWalker) Visit(node ast.Node) ast.Visitor {
39 | switch n := node.(type) {
40 | case *ast.Ident:
41 | // Ignore the blank identifier
42 | if n.Name == "_" {
43 | return nil
44 | }
45 |
46 | // Ignore keywords
47 | if token.Lookup(n.Name) != token.IDENT {
48 | return nil
49 | }
50 |
51 | // We are only interested in variables
52 | if obj, ok := w.info.Uses[n]; ok {
53 | if _, ok := obj.(*types.Var); !ok {
54 | return nil
55 | }
56 | }
57 |
58 | // FIXME instead of manually creating a new node, clone it and trim the node from its comments and position https://github.com/zimmski/go-mutesting/issues/49
59 | w.identifiers = append(w.identifiers, &ast.Ident{
60 | Name: n.Name,
61 | })
62 |
63 | return nil
64 | case *ast.SelectorExpr:
65 | if !checkForSelectorExpr(n) {
66 | return nil
67 | }
68 |
69 | // Check if we need to instantiate the expression
70 | initialize := false
71 | if n.Sel != nil {
72 | if obj, ok := w.info.Uses[n.Sel]; ok {
73 | t := obj.Type()
74 |
75 | switch t.Underlying().(type) {
76 | case *types.Array, *types.Map, *types.Slice, *types.Struct:
77 | initialize = true
78 | }
79 | }
80 | }
81 |
82 | if initialize {
83 | // FIXME we need to clone the node and trim comments and position recursively https://github.com/zimmski/go-mutesting/issues/49
84 | w.identifiers = append(w.identifiers, &ast.CompositeLit{
85 | Type: n,
86 | })
87 | } else {
88 | // FIXME we need to clone the node and trim comments and position recursively https://github.com/zimmski/go-mutesting/issues/49
89 | w.identifiers = append(w.identifiers, n)
90 | }
91 |
92 | return nil
93 | }
94 |
95 | return w
96 | }
97 |
98 | // Functions returns all found functions.
99 | func Functions(n ast.Node) []*ast.FuncDecl {
100 | w := &functionWalker{}
101 |
102 | ast.Walk(w, n)
103 |
104 | return w.functions
105 | }
106 |
107 | type functionWalker struct {
108 | functions []*ast.FuncDecl
109 | }
110 |
111 | func (w *functionWalker) Visit(node ast.Node) ast.Visitor {
112 | switch n := node.(type) {
113 | case *ast.FuncDecl:
114 | w.functions = append(w.functions, n)
115 |
116 | return nil
117 | }
118 |
119 | return w
120 | }
121 |
--------------------------------------------------------------------------------
/walk.go:
--------------------------------------------------------------------------------
1 | package mutesting
2 |
3 | import (
4 | "fmt"
5 | "go/ast"
6 | "go/types"
7 | "strings"
8 |
9 | "github.com/zimmski/go-mutesting/mutator"
10 | )
11 |
12 | // CountWalk returns the number of corresponding mutations for a given mutator.
13 | // 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.
14 | func CountWalk(pkg *types.Package, info *types.Info, node ast.Node, m mutator.Mutator) int {
15 | w := &countWalk{
16 | count: 0,
17 | mutator: m,
18 | pkg: pkg,
19 | info: info,
20 | }
21 |
22 | ast.Walk(w, node)
23 |
24 | return w.count
25 | }
26 |
27 | type countWalk struct {
28 | count int
29 | mutator mutator.Mutator
30 | pkg *types.Package
31 | info *types.Info
32 | }
33 |
34 | // Visit implements the Visit method of the ast.Visitor interface
35 | func (w *countWalk) Visit(node ast.Node) ast.Visitor {
36 | if node == nil {
37 | return w
38 | }
39 |
40 | w.count += len(w.mutator(w.pkg, w.info, node))
41 |
42 | return w
43 | }
44 |
45 | // MutateWalk mutates the given node with the given mutator returning a channel to control the mutation steps.
46 | // It traverses the AST of the given node and calls the method Check of the given mutator to verify that a node can be mutated by the mutator. If a node can be mutated the method Mutate of the given mutator is executed with the node and the control channel. After completion of the traversal the control channel is closed.
47 | func MutateWalk(pkg *types.Package, info *types.Info, node ast.Node, m mutator.Mutator) chan bool {
48 | w := &mutateWalk{
49 | changed: make(chan bool),
50 | mutator: m,
51 | pkg: pkg,
52 | info: info,
53 | }
54 |
55 | go func() {
56 | ast.Walk(w, node)
57 |
58 | close(w.changed)
59 | }()
60 |
61 | return w.changed
62 | }
63 |
64 | type mutateWalk struct {
65 | changed chan bool
66 | mutator mutator.Mutator
67 | pkg *types.Package
68 | info *types.Info
69 | }
70 |
71 | // Visit implements the Visit method of the ast.Visitor interface
72 | func (w *mutateWalk) Visit(node ast.Node) ast.Visitor {
73 | if node == nil {
74 | return w
75 | }
76 |
77 | for _, m := range w.mutator(w.pkg, w.info, node) {
78 | m.Change()
79 | w.changed <- true
80 | <-w.changed
81 |
82 | m.Reset()
83 | w.changed <- true
84 | <-w.changed
85 | }
86 |
87 | return w
88 | }
89 |
90 | // PrintWalk traverses the AST of the given node and prints every node to STDOUT.
91 | func PrintWalk(node ast.Node) {
92 | w := &printWalk{
93 | level: 0,
94 | }
95 |
96 | ast.Walk(w, node)
97 | }
98 |
99 | type printWalk struct {
100 | level int
101 | }
102 |
103 | // Visit implements the Visit method of the ast.Visitor interface
104 | func (w *printWalk) Visit(node ast.Node) ast.Visitor {
105 | if node != nil {
106 | w.level++
107 |
108 | fmt.Printf("%s(%p)%#v\n", strings.Repeat("\t", w.level), node, node)
109 | } else {
110 | w.level--
111 | }
112 |
113 | return w
114 | }
115 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
3 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5 | github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
6 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
7 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
8 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
9 | github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
10 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
11 | github.com/kisielk/errcheck v1.2.0 h1:reN85Pxc5larApoH1keMBiu2GWtPqXQ1nc9gx+jOU+E=
12 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
13 | github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
14 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
15 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
16 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
17 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
18 | github.com/mattn/goveralls v0.0.3 h1:GnFhBAK0wJmxZBum88FqDzcDPLjAk9sL0HzhmW+9bo8=
19 | github.com/mattn/goveralls v0.0.3/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
20 | github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5 h1:8Q0qkMVC/MmWkpIdlvZgcv2o2jrlF6zqVOh7W5YHdMA=
21 | github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
22 | github.com/onsi/ginkgo v1.10.2 h1:uqH7bpe+ERSiDa34FDOF7RikN6RzXgduUF8yarlZp94=
23 | github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
24 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
25 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
26 | github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
27 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
28 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
29 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
30 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
31 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
32 | github.com/zimmski/go-tool v0.0.0-20150119110811-2dfdc9ac8439 h1:yHqsjUkj0HWbKPw/6ZqC0/eMklaRpqubA199vaRLzzE=
33 | github.com/zimmski/go-tool v0.0.0-20150119110811-2dfdc9ac8439/go.mod h1:G4FVqCRvfz74AEB1crDNdQuvMfOoKtk7DlePsnV2yGs=
34 | github.com/zimmski/osutil v0.0.0-20190128123334-0d0b3ca231ac h1:uiFRlKzyIzHeLOthe0ethUkSGW7POlqxU3Tc21R8QpQ=
35 | github.com/zimmski/osutil v0.0.0-20190128123334-0d0b3ca231ac/go.mod h1:wJ9WGevuM/rw8aB2pQPFMUgXZWeaouI0ueFamR0DUPE=
36 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
37 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
38 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
39 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
40 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e h1:JgcxKXxCjrA2tyDP/aNU9K0Ck5Czfk6C7e2tMw7+bSI=
41 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
42 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
43 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
44 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
45 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
46 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
47 | golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
48 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
49 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
50 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
51 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
52 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
53 | golang.org/x/tools v0.0.0-20190820033707-85edb9ef3283 h1:Seblypk5Prvsc9UDjPTV/1N+YjNIXQntlZt0EpobDww=
54 | golang.org/x/tools v0.0.0-20190820033707-85edb9ef3283/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
55 | golang.org/x/tools v0.0.0-20191018212557-ed542cd5b28a h1:UuQ+70Pi/ZdWHuP4v457pkXeOynTdgd/4enxeIO/98k=
56 | golang.org/x/tools v0.0.0-20191018212557-ed542cd5b28a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
57 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
58 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
59 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
60 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
61 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
62 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
63 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
64 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
65 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
66 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
67 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
68 | honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
69 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
70 |
--------------------------------------------------------------------------------
/cmd/go-mutesting/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "crypto/md5"
6 | "fmt"
7 | "go/ast"
8 | "go/format"
9 | "go/printer"
10 | "go/token"
11 | "go/types"
12 | "io"
13 | "io/ioutil"
14 | "os"
15 | "os/exec"
16 | "path/filepath"
17 | "regexp"
18 | "strings"
19 | "syscall"
20 |
21 | "github.com/jessevdk/go-flags"
22 | "github.com/zimmski/go-tool/importing"
23 | "github.com/zimmski/osutil"
24 |
25 | "github.com/zimmski/go-mutesting"
26 | "github.com/zimmski/go-mutesting/astutil"
27 | "github.com/zimmski/go-mutesting/mutator"
28 | _ "github.com/zimmski/go-mutesting/mutator/branch"
29 | _ "github.com/zimmski/go-mutesting/mutator/expression"
30 | _ "github.com/zimmski/go-mutesting/mutator/statement"
31 | )
32 |
33 | const (
34 | returnOk = iota
35 | returnHelp
36 | returnBashCompletion
37 | returnError
38 | )
39 |
40 | type options struct {
41 | General struct {
42 | Debug bool `long:"debug" description:"Debug log output"`
43 | DoNotRemoveTmpFolder bool `long:"do-not-remove-tmp-folder" description:"Do not remove the tmp folder where all mutations are saved to"`
44 | Help bool `long:"help" description:"Show this help message"`
45 | Verbose bool `long:"verbose" description:"Verbose log output"`
46 | } `group:"General options"`
47 |
48 | Files struct {
49 | Blacklist []string `long:"blacklist" description:"List of MD5 checksums of mutations which should be ignored. Each checksum must end with a new line character."`
50 | ListFiles bool `long:"list-files" description:"List found files"`
51 | PrintAST bool `long:"print-ast" description:"Print the ASTs of all given files and exit"`
52 | } `group:"File options"`
53 |
54 | Mutator struct {
55 | DisableMutators []string `long:"disable" description:"Disable mutator by their name or using * as a suffix pattern"`
56 | ListMutators bool `long:"list-mutators" description:"List all available mutators"`
57 | } `group:"Mutator options"`
58 |
59 | Filter struct {
60 | Match string `long:"match" description:"Only functions are mutated that confirm to the arguments regex"`
61 | } `group:"Filter options"`
62 |
63 | Exec struct {
64 | Exec string `long:"exec" description:"Execute this command for every mutation (by default the built-in exec command is used)"`
65 | NoExec bool `long:"no-exec" description:"Skip the built-in exec command and just generate the mutations"`
66 | Timeout uint `long:"exec-timeout" description:"Sets a timeout for the command execution (in seconds)" default:"10"`
67 | } `group:"Exec options"`
68 |
69 | Test struct {
70 | Recursive bool `long:"test-recursive" description:"Defines if the executer should test recursively"`
71 | } `group:"Test options"`
72 |
73 | Remaining struct {
74 | Targets []string `description:"Packages, directories and files even with patterns (by default the current directory)"`
75 | } `positional-args:"true" required:"true"`
76 | }
77 |
78 | func checkArguments(args []string, opts *options) (bool, int) {
79 | p := flags.NewNamedParser("go-mutesting", flags.None)
80 |
81 | p.ShortDescription = "Mutation testing for Go source code"
82 |
83 | if _, err := p.AddGroup("go-mutesting", "go-mutesting arguments", opts); err != nil {
84 | return true, exitError(err.Error())
85 | }
86 |
87 | completion := len(os.Getenv("GO_FLAGS_COMPLETION")) > 0
88 |
89 | _, err := p.ParseArgs(args)
90 | if (opts.General.Help || len(args) == 0) && !completion {
91 | p.WriteHelp(os.Stdout)
92 |
93 | return true, returnHelp
94 | } else if opts.Mutator.ListMutators {
95 | for _, name := range mutator.List() {
96 | fmt.Println(name)
97 | }
98 |
99 | return true, returnOk
100 | }
101 |
102 | if err != nil {
103 | return true, exitError(err.Error())
104 | }
105 |
106 | if completion {
107 | return true, returnBashCompletion
108 | }
109 |
110 | if opts.General.Debug {
111 | opts.General.Verbose = true
112 | }
113 |
114 | return false, 0
115 | }
116 |
117 | func debug(opts *options, format string, args ...interface{}) {
118 | if opts.General.Debug {
119 | fmt.Printf(format+"\n", args...)
120 | }
121 | }
122 |
123 | func verbose(opts *options, format string, args ...interface{}) {
124 | if opts.General.Verbose || opts.General.Debug {
125 | fmt.Printf(format+"\n", args...)
126 | }
127 | }
128 |
129 | func exitError(format string, args ...interface{}) int {
130 | _, _ = fmt.Fprintf(os.Stderr, format+"\n", args...)
131 |
132 | return returnError
133 | }
134 |
135 | type mutatorItem struct {
136 | Name string
137 | Mutator mutator.Mutator
138 | }
139 |
140 | type mutationStats struct {
141 | passed int
142 | failed int
143 | duplicated int
144 | skipped int
145 | }
146 |
147 | func (ms *mutationStats) Score() float64 {
148 | total := ms.Total()
149 |
150 | if total == 0 {
151 | return 0.0
152 | }
153 |
154 | return float64(ms.passed) / float64(total)
155 | }
156 |
157 | func (ms *mutationStats) Total() int {
158 | return ms.passed + ms.failed + ms.skipped
159 | }
160 |
161 | func mainCmd(args []string) int {
162 | var opts = &options{}
163 | var mutationBlackList = map[string]struct{}{}
164 |
165 | if exit, exitCode := checkArguments(args, opts); exit {
166 | return exitCode
167 | }
168 |
169 | files := importing.FilesOfArgs(opts.Remaining.Targets)
170 | if len(files) == 0 {
171 | return exitError("Could not find any suitable Go source files")
172 | }
173 |
174 | if opts.Files.ListFiles {
175 | for _, file := range files {
176 | fmt.Println(file)
177 | }
178 |
179 | return returnOk
180 | } else if opts.Files.PrintAST {
181 | for _, file := range files {
182 | fmt.Println(file)
183 |
184 | src, _, err := mutesting.ParseFile(file)
185 | if err != nil {
186 | return exitError("Could not open file %q: %v", file, err)
187 | }
188 |
189 | mutesting.PrintWalk(src)
190 |
191 | fmt.Println()
192 | }
193 |
194 | return returnOk
195 | }
196 |
197 | if len(opts.Files.Blacklist) > 0 {
198 | for _, f := range opts.Files.Blacklist {
199 | c, err := ioutil.ReadFile(f)
200 | if err != nil {
201 | return exitError("Cannot read blacklist file %q: %v", f, err)
202 | }
203 |
204 | for _, line := range strings.Split(string(c), "\n") {
205 | if line == "" {
206 | continue
207 | }
208 |
209 | if len(line) != 32 {
210 | return exitError("%q is not a MD5 checksum", line)
211 | }
212 |
213 | mutationBlackList[line] = struct{}{}
214 | }
215 | }
216 | }
217 |
218 | var mutators []mutatorItem
219 |
220 | MUTATOR:
221 | for _, name := range mutator.List() {
222 | if len(opts.Mutator.DisableMutators) > 0 {
223 | for _, d := range opts.Mutator.DisableMutators {
224 | pattern := strings.HasSuffix(d, "*")
225 |
226 | if (pattern && strings.HasPrefix(name, d[:len(d)-2])) || (!pattern && name == d) {
227 | continue MUTATOR
228 | }
229 | }
230 | }
231 |
232 | verbose(opts, "Enable mutator %q", name)
233 |
234 | m, _ := mutator.New(name)
235 | mutators = append(mutators, mutatorItem{
236 | Name: name,
237 | Mutator: m,
238 | })
239 | }
240 |
241 | tmpDir, err := ioutil.TempDir("", "go-mutesting-")
242 | if err != nil {
243 | panic(err)
244 | }
245 | verbose(opts, "Save mutations into %q", tmpDir)
246 |
247 | var execs []string
248 | if opts.Exec.Exec != "" {
249 | execs = strings.Split(opts.Exec.Exec, " ")
250 | }
251 |
252 | stats := &mutationStats{}
253 |
254 | for _, file := range files {
255 | verbose(opts, "Mutate %q", file)
256 |
257 | src, fset, pkg, info, err := mutesting.ParseAndTypeCheckFile(file)
258 | if err != nil {
259 | return exitError(err.Error())
260 | }
261 |
262 | err = os.MkdirAll(tmpDir+"/"+filepath.Dir(file), 0755)
263 | if err != nil {
264 | panic(err)
265 | }
266 |
267 | tmpFile := tmpDir + "/" + file
268 |
269 | originalFile := fmt.Sprintf("%s.original", tmpFile)
270 | err = osutil.CopyFile(file, originalFile)
271 | if err != nil {
272 | panic(err)
273 | }
274 | debug(opts, "Save original into %q", originalFile)
275 |
276 | mutationID := 0
277 |
278 | if opts.Filter.Match != "" {
279 | m, err := regexp.Compile(opts.Filter.Match)
280 | if err != nil {
281 | return exitError("Match regex is not valid: %v", err)
282 | }
283 |
284 | for _, f := range astutil.Functions(src) {
285 | if m.MatchString(f.Name.Name) {
286 | mutationID = mutate(opts, mutators, mutationBlackList, mutationID, pkg, info, file, fset, src, f, tmpFile, execs, stats)
287 | }
288 | }
289 | } else {
290 | _ = mutate(opts, mutators, mutationBlackList, mutationID, pkg, info, file, fset, src, src, tmpFile, execs, stats)
291 | }
292 | }
293 |
294 | if !opts.General.DoNotRemoveTmpFolder {
295 | err = os.RemoveAll(tmpDir)
296 | if err != nil {
297 | panic(err)
298 | }
299 | debug(opts, "Remove %q", tmpDir)
300 | }
301 |
302 | if !opts.Exec.NoExec {
303 | fmt.Printf("The mutation score is %f (%d passed, %d failed, %d duplicated, %d skipped, total is %d)\n", stats.Score(), stats.passed, stats.failed, stats.duplicated, stats.skipped, stats.Total())
304 | } else {
305 | fmt.Println("Cannot do a mutation testing summary since no exec command was executed.")
306 | }
307 |
308 | return returnOk
309 | }
310 |
311 | func mutate(opts *options, mutators []mutatorItem, mutationBlackList map[string]struct{}, mutationID int, pkg *types.Package, info *types.Info, file string, fset *token.FileSet, src ast.Node, node ast.Node, tmpFile string, execs []string, stats *mutationStats) int {
312 | for _, m := range mutators {
313 | debug(opts, "Mutator %s", m.Name)
314 |
315 | changed := mutesting.MutateWalk(pkg, info, node, m.Mutator)
316 |
317 | for {
318 | _, ok := <-changed
319 |
320 | if !ok {
321 | break
322 | }
323 |
324 | mutationFile := fmt.Sprintf("%s.%d", tmpFile, mutationID)
325 | checksum, duplicate, err := saveAST(mutationBlackList, mutationFile, fset, src)
326 | if err != nil {
327 | fmt.Printf("INTERNAL ERROR %s\n", err.Error())
328 | } else if duplicate {
329 | debug(opts, "%q is a duplicate, we ignore it", mutationFile)
330 |
331 | stats.duplicated++
332 | } else {
333 | debug(opts, "Save mutation into %q with checksum %s", mutationFile, checksum)
334 |
335 | if !opts.Exec.NoExec {
336 | execExitCode := mutateExec(opts, pkg, file, src, mutationFile, execs)
337 |
338 | debug(opts, "Exited with %d", execExitCode)
339 |
340 | msg := fmt.Sprintf("%q with checksum %s", mutationFile, checksum)
341 |
342 | switch execExitCode {
343 | case 0:
344 | fmt.Printf("PASS %s\n", msg)
345 |
346 | stats.passed++
347 | case 1:
348 | fmt.Printf("FAIL %s\n", msg)
349 |
350 | stats.failed++
351 | case 2:
352 | fmt.Printf("SKIP %s\n", msg)
353 |
354 | stats.skipped++
355 | default:
356 | fmt.Printf("UNKOWN exit code for %s\n", msg)
357 | }
358 | }
359 | }
360 |
361 | changed <- true
362 |
363 | // Ignore original state
364 | <-changed
365 | changed <- true
366 |
367 | mutationID++
368 | }
369 | }
370 |
371 | return mutationID
372 | }
373 |
374 | func mutateExec(opts *options, pkg *types.Package, file string, src ast.Node, mutationFile string, execs []string) (execExitCode int) {
375 | if len(execs) == 0 {
376 | debug(opts, "Execute built-in exec command for mutation")
377 |
378 | diff, err := exec.Command("diff", "-u", file, mutationFile).CombinedOutput()
379 | if err == nil {
380 | execExitCode = 0
381 | } else if e, ok := err.(*exec.ExitError); ok {
382 | execExitCode = e.Sys().(syscall.WaitStatus).ExitStatus()
383 | } else {
384 | panic(err)
385 | }
386 | if execExitCode != 0 && execExitCode != 1 {
387 | fmt.Printf("%s\n", diff)
388 |
389 | panic("Could not execute diff on mutation file")
390 | }
391 |
392 | defer func() {
393 | _ = os.Rename(file+".tmp", file)
394 | }()
395 |
396 | err = os.Rename(file, file+".tmp")
397 | if err != nil {
398 | panic(err)
399 | }
400 | err = osutil.CopyFile(mutationFile, file)
401 | if err != nil {
402 | panic(err)
403 | }
404 |
405 | pkgName := pkg.Path()
406 | if opts.Test.Recursive {
407 | pkgName += "/..."
408 | }
409 |
410 | test, err := exec.Command("go", "test", "-timeout", fmt.Sprintf("%ds", opts.Exec.Timeout), pkgName).CombinedOutput()
411 | if err == nil {
412 | execExitCode = 0
413 | } else if e, ok := err.(*exec.ExitError); ok {
414 | execExitCode = e.Sys().(syscall.WaitStatus).ExitStatus()
415 | } else {
416 | panic(err)
417 | }
418 |
419 | if opts.General.Debug {
420 | fmt.Printf("%s\n", test)
421 | }
422 |
423 | switch execExitCode {
424 | case 0: // Tests passed -> FAIL
425 | fmt.Printf("%s\n", diff)
426 |
427 | execExitCode = 1
428 | case 1: // Tests failed -> PASS
429 | if opts.General.Debug {
430 | fmt.Printf("%s\n", diff)
431 | }
432 |
433 | execExitCode = 0
434 | case 2: // Did not compile -> SKIP
435 | if opts.General.Verbose {
436 | fmt.Println("Mutation did not compile")
437 | }
438 |
439 | if opts.General.Debug {
440 | fmt.Printf("%s\n", diff)
441 | }
442 | default: // Unknown exit code -> SKIP
443 | fmt.Println("Unknown exit code")
444 | fmt.Printf("%s\n", diff)
445 | }
446 |
447 | return execExitCode
448 | }
449 |
450 | debug(opts, "Execute %q for mutation", opts.Exec.Exec)
451 |
452 | execCommand := exec.Command(execs[0], execs[1:]...)
453 |
454 | execCommand.Stderr = os.Stderr
455 | execCommand.Stdout = os.Stdout
456 |
457 | execCommand.Env = append(os.Environ(), []string{
458 | "MUTATE_CHANGED=" + mutationFile,
459 | fmt.Sprintf("MUTATE_DEBUG=%t", opts.General.Debug),
460 | "MUTATE_ORIGINAL=" + file,
461 | "MUTATE_PACKAGE=" + pkg.Path(),
462 | fmt.Sprintf("MUTATE_TIMEOUT=%d", opts.Exec.Timeout),
463 | fmt.Sprintf("MUTATE_VERBOSE=%t", opts.General.Verbose),
464 | }...)
465 | if opts.Test.Recursive {
466 | execCommand.Env = append(execCommand.Env, "TEST_RECURSIVE=true")
467 | }
468 |
469 | err := execCommand.Start()
470 | if err != nil {
471 | panic(err)
472 | }
473 |
474 | // TODO timeout here
475 |
476 | err = execCommand.Wait()
477 |
478 | if err == nil {
479 | execExitCode = 0
480 | } else if e, ok := err.(*exec.ExitError); ok {
481 | execExitCode = e.Sys().(syscall.WaitStatus).ExitStatus()
482 | } else {
483 | panic(err)
484 | }
485 |
486 | return execExitCode
487 | }
488 |
489 | func main() {
490 | os.Exit(mainCmd(os.Args[1:]))
491 | }
492 |
493 | func saveAST(mutationBlackList map[string]struct{}, file string, fset *token.FileSet, node ast.Node) (string, bool, error) {
494 | var buf bytes.Buffer
495 |
496 | h := md5.New()
497 |
498 | err := printer.Fprint(io.MultiWriter(h, &buf), fset, node)
499 | if err != nil {
500 | return "", false, err
501 | }
502 |
503 | checksum := fmt.Sprintf("%x", h.Sum(nil))
504 |
505 | if _, ok := mutationBlackList[checksum]; ok {
506 | return checksum, true, nil
507 | }
508 |
509 | mutationBlackList[checksum] = struct{}{}
510 |
511 | src, err := format.Source(buf.Bytes())
512 | if err != nil {
513 | return "", false, err
514 | }
515 |
516 | err = ioutil.WriteFile(file, src, 0666)
517 | if err != nil {
518 | return "", false, err
519 | }
520 |
521 | return checksum, false, nil
522 | }
523 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # go-mutesting [](https://godoc.org/github.com/zimmski/go-mutesting) [](https://travis-ci.org/zimmski/go-mutesting) [](https://coveralls.io/r/zimmski/go-mutesting?branch=master)
2 |
3 | go-mutesting is a framework for performing mutation testing on Go source code. Its main purpose is to find source code, which is not covered by any tests.
4 |
5 | ## Quick example
6 |
7 | The following command mutates the go-mutesting project with all available mutators.
8 |
9 | ```bash
10 | go-mutesting github.com/zimmski/go-mutesting/...
11 | ```
12 |
13 | The execution of this command prints for every mutation if it was successfully tested or not. If not, the source code patch is printed out, so the mutation can be investigated. The following shows an example for a patch of a mutation.
14 |
15 | ```diff
16 | for _, d := range opts.Mutator.DisableMutators {
17 | pattern := strings.HasSuffix(d, "*")
18 |
19 | - if (pattern && strings.HasPrefix(name, d[:len(d)-2])) || (!pattern && name == d) {
20 | + if (pattern && strings.HasPrefix(name, d[:len(d)-2])) || false {
21 | continue MUTATOR
22 | }
23 | }
24 | ```
25 |
26 | The example shows that the right term `(!pattern && name == d)` of the `||` operator is made irrelevant by substituting it with `false`. Since this change of the source code is not detected by the test suite, meaning the test suite did not fail, we can mark it as untested code.
27 |
28 | The next mutation shows code from the `removeNode` method of a [linked list](https://github.com/zimmski/container/blob/master/list/linkedlist/linkedlist.go) implementation.
29 |
30 | ```diff
31 | }
32 |
33 | l.first = nil
34 | - l.last = nil
35 | +
36 | l.len = 0
37 | }
38 | ```
39 |
40 | We know that the code originates from a remove method which means that the mutation introduces a leak by ignoring the removal of a reference. This can be [tested](https://github.com/zimmski/container/commit/142c3e16a249095b0d63f2b41055d17cf059f045) with [go-leaks](https://github.com/zimmski/go-leak).
41 |
42 | ## Table of content
43 |
44 | - [What is mutation testing?](#what-is-mutation-testing)
45 | - [How do I use go-mutesting?](#how-do-i-use-go-mutesting)
46 | - [How do I write my own mutation exec commands?](#write-mutation-exec-commands)
47 | - [Which mutators are implemented?](#list-of-mutators)
48 | - [Other mutation testing projects and their flaws](#other-projects)
49 | - [Can I make feature requests and report bugs and problems?](#feature-request)
50 |
51 | ## What is mutation testing?
52 |
53 | The definition of mutation testing is best quoted from Wikipedia:
54 |
55 | > Mutation testing (or Mutation analysis or Program mutation) is used to design new software tests and evaluate the quality of existing software tests. Mutation testing involves modifying a program in small ways. Each mutated version is called a mutant and tests detect and reject mutants by causing the behavior of the original version to differ from the mutant. This is called killing the mutant. Test suites are measured by the percentage of mutants that they kill. New tests can be designed to kill additional mutants.
56 | >
-- [https://en.wikipedia.org/wiki/Mutation_testing](https://en.wikipedia.org/wiki/Mutation_testing)
57 |
58 | > Tests can be created to verify the correctness of the implementation of a given software system, but the creation of tests still poses the question whether the tests are correct and sufficiently cover the requirements that have originated the implementation.
59 | >
-- [https://en.wikipedia.org/wiki/Mutation_testing](https://en.wikipedia.org/wiki/Mutation_testing)
60 |
61 | 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.
62 |
63 | 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).
64 |
65 | ## How do I use go-mutesting?
66 |
67 | go-mutesting includes a binary which is go-getable.
68 |
69 | ```bash
70 | go get -t -v github.com/zimmski/go-mutesting/...
71 | ```
72 |
73 | The binary's help can be invoked by executing the binary without arguments or with the `--help` argument.
74 |
75 | ```bash
76 | go-mutesting --help
77 | ```
78 |
79 | > **Note**: This README describes only a few of the available arguments. It is therefore advisable to examine the output of the `--help` argument.
80 |
81 | The targets of the mutation testing can be defined as arguments to the binary. Every target can be either a Go source file, a directory or a package. Directories and packages can also include the `...` wildcard pattern which will search recursively for Go source files. Test source files with the suffix `_test` are excluded, since this would interfere with the testing process most of the time.
82 |
83 | The following example gathers all Go files which are defined by the targets and generate mutations with all available mutators of the binary.
84 |
85 | ```bash
86 | go-mutesting parse.go example/ github.com/zimmski/go-mutesting/mutator/...
87 | ```
88 |
89 | Every mutation has to be tested using an [exec command](#write-mutation-exec-commands). By default the built-in exec command is used, which tests a mutation using the following steps:
90 |
91 | - Replace the original file with the mutation.
92 | - Execute all tests of the package of the mutated file.
93 | - Report if the mutation was killed.
94 |
95 | Alternatively the `--exec` argument can be used to invoke an external exec command. The [/scripts/exec](/scripts/exec) directory holds basic exec commands for Go projects. The [test-mutated-package.sh](/scripts/exec/test-mutated-package.sh) script implements all steps and almost all features of the built-in exec command. It can be for example used to test the [github.com/zimmski/go-mutesting/example](/example) package.
96 |
97 | ```bash
98 | go-mutesting --exec "$GOPATH/src/github.com/zimmski/go-mutesting/scripts/exec/test-mutated-package.sh" github.com/zimmski/go-mutesting/example
99 | ```
100 |
101 | The execution will print the following output.
102 |
103 | > **Note**: This output is from an older version of go-mutesting. Up to date versions of go-mutesting will have different mutations.
104 |
105 | ```diff
106 | PASS "/tmp/go-mutesting-422402775//home/zimmski/go/src/github.com/zimmski/go-mutesting/example/example.go.0" with checksum b705f4c99e6d572de509609eb0a625be
107 | PASS "/tmp/go-mutesting-422402775//home/zimmski/go/src/github.com/zimmski/go-mutesting/example/example.go.1" with checksum eb54efffc5edfc7eba2b276371b29836
108 | PASS "/tmp/go-mutesting-422402775//home/zimmski/go/src/github.com/zimmski/go-mutesting/example/example.go.2" with checksum 011df9567e5fee9bf75cbe5d5dc1c81f
109 | --- /home/zimmski/go/src/github.com/zimmski/go-mutesting/example/example.go
110 | +++ /tmp/go-mutesting-422402775//home/zimmski/go/src/github.com/zimmski/go-mutesting/example/example.go.3
111 | @@ -16,7 +16,7 @@
112 | }
113 |
114 | if n < 0 {
115 | - n = 0
116 | +
117 | }
118 |
119 | n++
120 | FAIL "/tmp/go-mutesting-422402775//home/zimmski/go/src/github.com/zimmski/go-mutesting/example/example.go.3" with checksum 82fc14acf7b561598bfce25bf3a162a2
121 | PASS "/tmp/go-mutesting-422402775//home/zimmski/go/src/github.com/zimmski/go-mutesting/example/example.go.4" with checksum 5720f1bf404abea121feb5a50caf672c
122 | PASS "/tmp/go-mutesting-422402775//home/zimmski/go/src/github.com/zimmski/go-mutesting/example/example.go.5" with checksum d6c1b5e25241453128f9f3bf1b9e7741
123 | --- /home/zimmski/go/src/github.com/zimmski/go-mutesting/example/example.go
124 | +++ /tmp/go-mutesting-422402775//home/zimmski/go/src/github.com/zimmski/go-mutesting/example/example.go.6
125 | @@ -24,7 +24,6 @@
126 | n += bar()
127 |
128 | bar()
129 | - bar()
130 |
131 | return n
132 | }
133 | FAIL "/tmp/go-mutesting-422402775//home/zimmski/go/src/github.com/zimmski/go-mutesting/example/example.go.6" with checksum 5b1ca0cfedd786d9df136a0e042df23a
134 | PASS "/tmp/go-mutesting-422402775//home/zimmski/go/src/github.com/zimmski/go-mutesting/example/example.go.8" with checksum 6928f4458787c7042c8b4505888300a6
135 | The mutation score is 0.750000 (6 passed, 2 failed, 0 skipped, total is 8)
136 | ```
137 |
138 | The output shows that eight mutations have been found and tested. Six of them passed which means that the test suite failed for these mutations and the mutations were therefore killed. However, two mutations did not fail the test suite. Their source code patches are shown in the output which can be used to investigate these mutations.
139 |
140 | 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 number of passed mutations by the number of total mutations, for the example above this would be 6/8=0.75. A score of 1.0 means that all mutations have been killed.
141 |
142 | ### Blacklist false positives
143 |
144 | Mutation testing can generate many false positives since mutation algorithms do not fully understand the given source code. `early exits` are one common example. They can be implemented as optimizations and will almost always trigger a false-positive since the unoptimized code path will be used which will lead to the same result. go-mutesting is meant to be used as an addition to automatic test suites. It is therefore necessary to mark such mutations as false-positives. This is done with the `--blacklist` argument. The argument defines a file which contains in every line a MD5 checksum of a mutation. These checksums can then be used to ignore mutations.
145 |
146 | > **Note**: The blacklist feature is currently badly implemented as a change in the original source code will change all checksums.
147 |
148 | The example output of the [How do I use go-mutesting?](#how-do-i-use-go-mutesting) section describes a mutation `example.go.6` which has the checksum `5b1ca0cfedd786d9df136a0e042df23a`. If we want to mark this mutation as a false-positive, we simple create a file with the following content.
149 |
150 | ```
151 | 5b1ca0cfedd786d9df136a0e042df23a
152 | ```
153 |
154 | The blacklist file, which is named `example.blacklist` in this example, can then be used to invoke go-mutesting.
155 |
156 | ```bash
157 | go-mutesting --blacklist example.blacklist github.com/zimmski/go-mutesting/example
158 | ```
159 |
160 | The execution will print the following output.
161 |
162 | > **Note**: This output is from an older version of go-mutesting. Up to date versions of go-mutesting will have different mutations.
163 |
164 | ```diff
165 | PASS "/tmp/go-mutesting-208240643/example.go.0" with checksum b705f4c99e6d572de509609eb0a625be
166 | PASS "/tmp/go-mutesting-208240643/example.go.1" with checksum eb54efffc5edfc7eba2b276371b29836
167 | PASS "/tmp/go-mutesting-208240643/example.go.2" with checksum 011df9567e5fee9bf75cbe5d5dc1c81f
168 | --- example.go 2014-12-29 23:37:42.813320040 +0100
169 | +++ /tmp/go-mutesting-208240643/example.go.3 2014-12-30 00:49:33.573285038 +0100
170 | @@ -16,7 +16,7 @@
171 | }
172 |
173 | if n < 0 {
174 | - n = 0
175 | +
176 | }
177 |
178 | n++
179 | FAIL "/tmp/go-mutesting-208240643/example.go.3" with checksum 82fc14acf7b561598bfce25bf3a162a2
180 | PASS "/tmp/go-mutesting-208240643/example.go.4" with checksum 5720f1bf404abea121feb5a50caf672c
181 | PASS "/tmp/go-mutesting-208240643/example.go.5" with checksum d6c1b5e25241453128f9f3bf1b9e7741
182 | PASS "/tmp/go-mutesting-208240643/example.go.8" with checksum 6928f4458787c7042c8b4505888300a6
183 | The mutation score is 0.857143 (6 passed, 1 failed, 0 skipped, total is 7)
184 | ```
185 |
186 | By comparing this output to the original output we can state that we now have 7 mutations instead of 8.
187 |
188 | ## How do I write my own mutation exec commands?
189 |
190 | A mutation exec command is invoked for every mutation which is necessary to test a mutation. Commands should handle at least the following phases.
191 |
192 | 1. **Setup** the source to include the mutation.
193 | 2. **Test** the source by invoking the test suite and possible other test functionality.
194 | 3. **Cleanup** all changes and remove all temporary assets.
195 | 4. **Report** if the mutation was killed.
196 |
197 | It is important to note that each invocation should be isolated and therefore stateless. This means that an invocation must not interfere with other invocations.
198 |
199 | A set of environment variables, which define exactly one mutation, is passed on to the command.
200 |
201 | | Name | Description |
202 | | :-------------- | :------------------------------------------------------------------------ |
203 | | MUTATE_CHANGED | Defines the filename to the mutation of the original file. |
204 | | MUTATE_DEBUG | Defines if debugging output should be printed. |
205 | | MUTATE_ORIGINAL | Defines the filename to the original file which was mutated. |
206 | | MUTATE_PACKAGE | Defines the import path of the origianl file. |
207 | | MUTATE_TIMEOUT | Defines a timeout which should be taken into account by the exec command. |
208 | | MUTATE_VERBOSE | Defines if verbose output should be printed. |
209 | | TEST_RECURSIVE | Defines if tests should be run recursively. |
210 |
211 | A command must exit with an appropriate exit code.
212 |
213 | | Exit code | Description |
214 | | :------ | :-------- |
215 | | 0 | The mutation was killed. Which means that the test led to a failed test after the mutation was applied. |
216 | | 1 | The mutation is alive. Which means that this could be a flaw in the test suite or even in the implementation. |
217 | | 2 | The mutation was skipped, since there are other problems e.g. compilation errors. |
218 | | >2 | The mutation produced an unknown exit code which might be a flaw in the exec command. |
219 |
220 | Examples for exec commands can be found in the [scripts](/scripts/exec) directory.
221 |
222 | ## Which mutators are implemented?
223 |
224 | ### Branch mutators
225 |
226 | | Name | Description |
227 | | :------------ | :------------------------------------------------- |
228 | | branch/case | Empties case bodies. |
229 | | branch/if | Empties branches of `if` and `else if` statements. |
230 | | branch/else | Empties branches of `else` statements. |
231 |
232 | ### Expression mutators
233 |
234 | | Name | Description |
235 | | :-------------------- | :--------------------------------------------- |
236 | | expression/comparison | Searches for comparison operators, such as `>` and `<=`, and replaces them with similar operators to catch off-by-one errors, e.g. `>` is replaced by `>=`. |
237 | | expression/remove | Searches for `&&` and \|\| operators and makes each term of the operator irrelevant by using `true` or `false` as replacements. |
238 |
239 | ### Statement mutators
240 |
241 | | Name | Description |
242 | | :------------------ | :--------------------------------------------- |
243 | | statement/remove | Removes assignment, increment, decrement and expression statements. |
244 |
245 | ## How do I write my own mutators?
246 |
247 | 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.
248 |
249 | Additionally each mutator has to be registered with the `Register` function of the [github.com/zimmski/go-mutesting/mutator](https://godoc.org/github.com/zimmski/go-mutesting/mutator#Mutator) package to make it usable by the binary.
250 |
251 | Examples for mutators can be found in the [github.com/zimmski/go-mutesting/mutator](https://godoc.org/github.com/zimmski/go-mutesting/mutator) package and its sub-packages.
252 |
253 | ## Other mutation testing projects and their flaws
254 |
255 | go-mutesting is not the first project to implement mutation testing for Go source code. A quick search uncovers the following projects.
256 |
257 | - https://github.com/darkhelmet/manbearpig
258 | - https://github.com/kisielk/mutator
259 | - https://github.com/StefanSchroeder/Golang-Mutation-testing
260 |
261 | All of them have significant flaws in comparison to go-mutesting:
262 |
263 | - Only one type (or even one case) of mutation is implemented.
264 | - Can only be used for one mutator at a time (manbearpig, Golang-Mutation-testing).
265 | - Mutation is done by content which can lead to lots of invalid mutations (Golang-Mutation-testing).
266 | - New mutators are not easily implemented and integrated.
267 | - Can only be used for one package or file at a time.
268 | - Other scenarios as `go test` cannot be applied.
269 | - Do not properly clean up or handle fatal failures.
270 | - No automatic tests to ensure that the algorithms are working at all.
271 | - Uses another language (Golang-Mutation-testing).
272 |
273 | ## Can I make feature requests and report bugs and problems?
274 |
275 | Sure, just submit an [issue via the project tracker](https://github.com/zimmski/go-mutesting/issues/new) and I will see what I can do. Please note that I do not guarantee to implement anything soon and bugs and problems are more important to me than new features. If you need something implemented or fixed right away you can contact me via mail to do contract work for you.
276 |
--------------------------------------------------------------------------------