diff --git a/astutil/create.go b/astutil/create.go index 6c89dcc..dc68d5c 100644 --- a/astutil/create.go +++ b/astutil/create.go @@ -3,18 +3,19 @@ package astutil import ( "go/ast" "go/token" + "go/types" ) // CreateNoopOfStatement creates a syntactically safe noop statement out of a given statement. -func CreateNoopOfStatement(stmt ast.Stmt) ast.Stmt { - return CreateNoopOfStatements([]ast.Stmt{stmt}) +func CreateNoopOfStatement(pkg *types.Package, info *types.Info, stmt ast.Stmt) ast.Stmt { + return CreateNoopOfStatements(pkg, info, []ast.Stmt{stmt}) } // CreateNoopOfStatements creates a syntactically safe noop statement out of a given statement. -func CreateNoopOfStatements(stmts []ast.Stmt) ast.Stmt { +func CreateNoopOfStatements(pkg *types.Package, info *types.Info, stmts []ast.Stmt) ast.Stmt { var ids []ast.Expr for _, stmt := range stmts { - ids = append(ids, IdentifiersInStatement(stmt)...) + ids = append(ids, IdentifiersInStatement(pkg, info, stmt)...) } if len(ids) == 0 { diff --git a/astutil/query.go b/astutil/query.go index b13a838..88f672e 100644 --- a/astutil/query.go +++ b/astutil/query.go @@ -2,11 +2,16 @@ package astutil import ( "go/ast" + "go/token" + "go/types" ) // IdentifiersInStatement returns all identifiers with their found in a statement. -func IdentifiersInStatement(stmt ast.Stmt) []ast.Expr { - w := &identifierWalker{} +func IdentifiersInStatement(pkg *types.Package, info *types.Info, stmt ast.Stmt) []ast.Expr { + w := &identifierWalker{ + pkg: pkg, + info: info, + } ast.Walk(w, stmt) @@ -15,78 +20,8 @@ func IdentifiersInStatement(stmt ast.Stmt) []ast.Expr { type identifierWalker struct { identifiers []ast.Expr -} - -var blacklistedIdentifiers = map[string]bool{ - // blank identifier - "_": true, - // builtin - can be used as identifier but are unlikely to be in practice - // (except perhaps with panic, defer, recover, print, prinln?) - "bool": true, - "true": true, - "false": true, - "uint8": true, - "uint16": true, - "uint32": true, - "uint64": true, - "int8": true, - "int16": true, - "int32": true, - "int64": true, - "float32": true, - "float64": true, - "complex64": true, - "complex128": true, - "string": true, - "int": true, - "uint": true, - "uintptr": true, - "byte": true, - "rune": true, - "iota": true, - "nil": true, - "append": true, - "copy": true, - "delete": true, - "len": true, - "cap": true, - "make": true, - "new": true, - "complex": true, - "real": true, - "imag": true, - "close": true, - "panic": true, - "recover": true, - "print": true, - "println": true, - "error": true, - // reserved keywords - cannot be used as identifier. - "break": true, - "default": true, - "func": true, - "interface": true, - "select": true, - "case": true, - "defer": true, - "go": true, - "map": true, - "struct": true, - "chan": true, - "else": true, - "goto": true, - "package": true, - "switch": true, - "const": true, - "fallthrough": true, - "if": true, - "range": true, - "type": true, - "continue": true, - "for": true, - "import": true, - "return": true, - "var": true, + pkg *types.Package + info *types.Info } func checkForSelectorExpr(node ast.Expr) bool { @@ -103,13 +38,49 @@ func checkForSelectorExpr(node ast.Expr) bool { func (w *identifierWalker) Visit(node ast.Node) ast.Visitor { switch n := node.(type) { case *ast.Ident: - if _, ok := blacklistedIdentifiers[n.Name]; !ok { - w.identifiers = append(w.identifiers, n) + // Ignore the blank identifier + if n.Name == "_" { + return nil } + // Ignore keywords + if token.Lookup(n.Name) != token.IDENT { + return nil + } + + // We are only interested in variables + if obj, ok := w.info.Uses[n]; ok { + if _, ok := obj.(*types.Var); !ok { + return nil + } + } + + w.identifiers = append(w.identifiers, n) + return nil case *ast.SelectorExpr: - if checkForSelectorExpr(n) { + if !checkForSelectorExpr(n) { + return nil + } + + // Check if we need to instantiate the expression + initialize := false + if n.Sel != nil { + if obj, ok := w.info.Uses[n.Sel]; ok { + t := obj.Type() + + switch t.Underlying().(type) { + case *types.Array, *types.Map, *types.Slice, *types.Struct: + initialize = true + } + } + } + + if initialize { + w.identifiers = append(w.identifiers, &ast.CompositeLit{ + Type: n, + }) + } else { w.identifiers = append(w.identifiers, n) } diff --git a/cmd/go-mutesting/main.go b/cmd/go-mutesting/main.go index 7862660..d36a996 100644 --- a/cmd/go-mutesting/main.go +++ b/cmd/go-mutesting/main.go @@ -7,8 +7,10 @@ import ( "go/ast" "go/build" "go/format" + "go/importer" "go/printer" "go/token" + "go/types" "io" "io/ioutil" "os" @@ -144,9 +146,10 @@ type mutatorItem struct { } type mutationStats struct { - passed int - failed int - skipped int + passed int + failed int + duplicated int + skipped int } func mainCmd(args []string) int { @@ -250,6 +253,29 @@ MUTATOR: return exitError("Could not open file %q: %v", file, err) } + dir, err := filepath.Abs(filepath.Dir(file)) + if err != nil { + return exitError("Could not absolute the file path of %q: %v", file, err) + } + + buildPkg, err := build.ImportDir(dir, build.FindOnly) + if err != nil { + return exitError("Could create build package of %q: %v", file, err) + } + + conf := types.Config{ + Importer: importer.Default(), + } + + info := &types.Info{ + Uses: make(map[*ast.Ident]types.Object), + } + + pkg, err := conf.Check(buildPkg.ImportPath, fset, []*ast.File{src}, info) // TODO query the import path without the additional go/build.ImportDirt step + if err != nil { + return exitError("Could not type check file %q: %v", file, err) + } + err = os.MkdirAll(tmpDir+"/"+filepath.Dir(file), 0755) if err != nil { panic(err) @@ -274,11 +300,11 @@ MUTATOR: for _, f := range astutil.Functions(src) { if m.MatchString(f.Name.Name) { - mutationID = mutate(opts, mutators, mutationBlackList, mutationID, file, fset, src, f, tmpFile, execs, stats) + mutationID = mutate(opts, mutators, mutationBlackList, mutationID, pkg, info, file, fset, src, f, tmpFile, execs, stats) } } } else { - mutationID = mutate(opts, mutators, mutationBlackList, mutationID, file, fset, src, src, tmpFile, execs, stats) + mutationID = mutate(opts, mutators, mutationBlackList, mutationID, pkg, info, file, fset, src, src, tmpFile, execs, stats) } } @@ -291,7 +317,7 @@ MUTATOR: } if !opts.Exec.NoExec { - fmt.Printf("The mutation score is %f (%d passed, %d failed, %d skipped, total is %d)\n", float64(stats.passed)/float64(stats.passed+stats.failed), stats.passed, stats.failed, stats.skipped, stats.passed+stats.failed+stats.skipped) + fmt.Printf("The mutation score is %f (%d passed, %d failed, %d duplicated, %d skipped, total is %d)\n", float64(stats.passed)/float64(stats.passed+stats.failed), stats.passed, stats.failed, stats.duplicated, stats.skipped, stats.passed+stats.failed+stats.duplicated+stats.skipped) } else { fmt.Println("Cannot do a mutation testing summary since no exec command was executed.") } @@ -299,21 +325,11 @@ MUTATOR: return returnOk } -func mutate(opts *options, mutators []mutatorItem, mutationBlackList map[string]struct{}, mutationID int, file string, fset *token.FileSet, src ast.Node, node ast.Node, tmpFile string, execs []string, stats *mutationStats) int { - dir, err := filepath.Abs(filepath.Dir(file)) - if err != nil { - panic(err) - } - - pkg, err := build.ImportDir(dir, build.FindOnly) - if err != nil { - panic(err) - } - +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 { for _, m := range mutators { debug(opts, "Mutator %s", m.Name) - changed := mutesting.MutateWalk(node, m.Mutator) + changed := mutesting.MutateWalk(pkg, info, node, m.Mutator) for { _, ok := <-changed @@ -329,6 +345,8 @@ func mutate(opts *options, mutators []mutatorItem, mutationBlackList map[string] } if duplicate { debug(opts, "%q is a duplicate, we ignore it", mutationFile) + + stats.duplicated++ } else { debug(opts, "Save mutation into %q with checksum %s", mutationFile, checksum) @@ -371,7 +389,7 @@ func mutate(opts *options, mutators []mutatorItem, mutationBlackList map[string] return mutationID } -func mutateExec(opts *options, pkg *build.Package, file string, src ast.Node, mutationFile string, execs []string) (execExitCode int) { +func mutateExec(opts *options, pkg *types.Package, file string, src ast.Node, mutationFile string, execs []string) (execExitCode int) { if len(execs) == 0 { debug(opts, "Execute built-in exec command for mutation") @@ -402,7 +420,7 @@ func mutateExec(opts *options, pkg *build.Package, file string, src ast.Node, mu panic(err) } - pkgName := pkg.ImportPath + pkgName := pkg.Path() if opts.Test.Recursive { pkgName += "/..." } @@ -458,7 +476,7 @@ func mutateExec(opts *options, pkg *build.Package, file string, src ast.Node, mu "MUTATE_CHANGED=" + mutationFile, fmt.Sprintf("MUTATE_DEBUG=%t", opts.General.Debug), "MUTATE_ORIGINAL=" + file, - "MUTATE_PACKAGE=" + pkg.ImportPath, + "MUTATE_PACKAGE=" + pkg.Path(), fmt.Sprintf("MUTATE_TIMEOUT=%d", opts.Exec.Timeout), fmt.Sprintf("MUTATE_VERBOSE=%t", opts.General.Verbose), }...) diff --git a/cmd/go-mutesting/main_test.go b/cmd/go-mutesting/main_test.go index d1d1ccf..34e2a3a 100644 --- a/cmd/go-mutesting/main_test.go +++ b/cmd/go-mutesting/main_test.go @@ -15,7 +15,7 @@ func TestMain(t *testing.T) { "../../example", []string{"--debug", "--exec-timeout", "1"}, returnOk, - "The mutation score is 0.500000 (7 passed, 7 failed, 0 skipped, total is 14)", + "The mutation score is 0.538462 (7 passed, 6 failed, 9 duplicated, 0 skipped, total is 22)", ) } @@ -25,7 +25,7 @@ func TestMainRecursive(t *testing.T) { "../../example", []string{"--debug", "--exec-timeout", "1", "./..."}, returnOk, - "The mutation score is 0.533333 (8 passed, 7 failed, 0 skipped, total is 15)", + "The mutation score is 0.571429 (8 passed, 6 failed, 9 duplicated, 0 skipped, total is 23)", ) } @@ -35,7 +35,7 @@ func TestMainFromOtherDirectory(t *testing.T) { "../..", []string{"--debug", "--exec-timeout", "1", "github.com/zimmski/go-mutesting/example"}, returnOk, - "The mutation score is 0.500000 (7 passed, 7 failed, 0 skipped, total is 14)", + "The mutation score is 0.538462 (7 passed, 6 failed, 9 duplicated, 0 skipped, total is 22)", ) } @@ -45,7 +45,7 @@ func TestMainMatch(t *testing.T) { "../../example", []string{"--debug", "--exec", "../scripts/exec/test-mutated-package.sh", "--exec-timeout", "1", "--match", "baz", "./..."}, returnOk, - "The mutation score is 0.500000 (1 passed, 1 failed, 0 skipped, total is 2)", + "The mutation score is 0.500000 (1 passed, 1 failed, 0 duplicated, 0 skipped, total is 2)", ) } diff --git a/mutator/branch/mutatecase.go b/mutator/branch/mutatecase.go index b5b7229..ded96c5 100644 --- a/mutator/branch/mutatecase.go +++ b/mutator/branch/mutatecase.go @@ -2,6 +2,7 @@ package branch import ( "go/ast" + "go/types" "github.com/zimmski/go-mutesting/astutil" "github.com/zimmski/go-mutesting/mutator" @@ -12,7 +13,7 @@ func init() { } // MutatorCase implements a mutator for case clauses. -func MutatorCase(node ast.Node) []mutator.Mutation { +func MutatorCase(pkg *types.Package, info *types.Info, node ast.Node) []mutator.Mutation { n, ok := node.(*ast.CaseClause) if !ok { return nil @@ -24,7 +25,7 @@ func MutatorCase(node ast.Node) []mutator.Mutation { mutator.Mutation{ Change: func() { n.Body = []ast.Stmt{ - astutil.CreateNoopOfStatements(n.Body), + astutil.CreateNoopOfStatements(pkg, info, n.Body), } }, Reset: func() { diff --git a/mutator/branch/mutateelse.go b/mutator/branch/mutateelse.go index 22dccf8..d934e8a 100644 --- a/mutator/branch/mutateelse.go +++ b/mutator/branch/mutateelse.go @@ -2,6 +2,7 @@ package branch import ( "go/ast" + "go/types" "github.com/zimmski/go-mutesting/astutil" "github.com/zimmski/go-mutesting/mutator" @@ -12,7 +13,7 @@ func init() { } // MutatorElse implements a mutator for else branches. -func MutatorElse(node ast.Node) []mutator.Mutation { +func MutatorElse(pkg *types.Package, info *types.Info, node ast.Node) []mutator.Mutation { n, ok := node.(*ast.IfStmt) if !ok { return nil @@ -28,7 +29,7 @@ func MutatorElse(node ast.Node) []mutator.Mutation { return []mutator.Mutation{ mutator.Mutation{ Change: func() { - n.Else = astutil.CreateNoopOfStatement(old) + n.Else = astutil.CreateNoopOfStatement(pkg, info, old) }, Reset: func() { n.Else = old diff --git a/mutator/branch/mutateif.go b/mutator/branch/mutateif.go index 95d6950..91c46a5 100644 --- a/mutator/branch/mutateif.go +++ b/mutator/branch/mutateif.go @@ -2,6 +2,7 @@ package branch import ( "go/ast" + "go/types" "github.com/zimmski/go-mutesting/astutil" "github.com/zimmski/go-mutesting/mutator" @@ -12,7 +13,7 @@ func init() { } // MutatorIf implements a mutator for if and else if branches. -func MutatorIf(node ast.Node) []mutator.Mutation { +func MutatorIf(pkg *types.Package, info *types.Info, node ast.Node) []mutator.Mutation { n, ok := node.(*ast.IfStmt) if !ok { return nil @@ -24,7 +25,7 @@ func MutatorIf(node ast.Node) []mutator.Mutation { mutator.Mutation{ Change: func() { n.Body.List = []ast.Stmt{ - astutil.CreateNoopOfStatement(n.Body), + astutil.CreateNoopOfStatement(pkg, info, n.Body), } }, Reset: func() { diff --git a/mutator/expression/remove.go b/mutator/expression/remove.go index 4b54518..f18a314 100644 --- a/mutator/expression/remove.go +++ b/mutator/expression/remove.go @@ -3,6 +3,7 @@ package expression import ( "go/ast" "go/token" + "go/types" "github.com/zimmski/go-mutesting/mutator" ) @@ -12,7 +13,7 @@ func init() { } // MutatorRemoveTerm implements a mutator to remove expression terms. -func MutatorRemoveTerm(node ast.Node) []mutator.Mutation { +func MutatorRemoveTerm(pkg *types.Package, info *types.Info, node ast.Node) []mutator.Mutation { n, ok := node.(*ast.BinaryExpr) if !ok { return nil diff --git a/mutator/mutator.go b/mutator/mutator.go index 0ef83b2..c17e819 100644 --- a/mutator/mutator.go +++ b/mutator/mutator.go @@ -3,11 +3,12 @@ package mutator import ( "fmt" "go/ast" + "go/types" "sort" ) // Mutator defines a mutator for mutation testing by returning a list of possible mutations for the given node. -type Mutator func(node ast.Node) []Mutation +type Mutator func(pkg *types.Package, info *types.Info, node ast.Node) []Mutation var mutatorLookup = make(map[string]Mutator) diff --git a/mutator/mutator_test.go b/mutator/mutator_test.go index 9e18355..26f2c6c 100644 --- a/mutator/mutator_test.go +++ b/mutator/mutator_test.go @@ -2,12 +2,13 @@ package mutator import ( "go/ast" + "go/types" "testing" "github.com/stretchr/testify/assert" ) -func mockMutator(node ast.Node) []Mutation { +func mockMutator(pkg *types.Package, info *types.Info, node ast.Node) []Mutation { // Do nothing return nil diff --git a/mutator/statement/remove.go b/mutator/statement/remove.go index bdbf96f..dfadafe 100644 --- a/mutator/statement/remove.go +++ b/mutator/statement/remove.go @@ -3,6 +3,7 @@ package statement import ( "go/ast" "go/token" + "go/types" "github.com/zimmski/go-mutesting/astutil" "github.com/zimmski/go-mutesting/mutator" @@ -26,7 +27,7 @@ func checkRemoveStatement(node ast.Stmt) bool { } // MutatorRemoveStatement implements a mutator to remove statements. -func MutatorRemoveStatement(node ast.Node) []mutator.Mutation { +func MutatorRemoveStatement(pkg *types.Package, info *types.Info, node ast.Node) []mutator.Mutation { var l []ast.Stmt switch n := node.(type) { @@ -45,7 +46,7 @@ func MutatorRemoveStatement(node ast.Node) []mutator.Mutation { mutations = append(mutations, mutator.Mutation{ Change: func() { - l[li] = astutil.CreateNoopOfStatement(old) + l[li] = astutil.CreateNoopOfStatement(pkg, info, old) }, Reset: func() { l[li] = old diff --git a/mutator/statement/remove_test.go b/mutator/statement/remove_test.go index 6d35d99..8c0e2be 100644 --- a/mutator/statement/remove_test.go +++ b/mutator/statement/remove_test.go @@ -11,6 +11,6 @@ func TestMutatorRemoveStatement(t *testing.T) { t, MutatorRemoveStatement, "../../testdata/statement/remove.go", - 15, + 17, ) } diff --git a/test/mutator.go b/test/mutator.go index 76b35ff..9d4a785 100644 --- a/test/mutator.go +++ b/test/mutator.go @@ -3,7 +3,10 @@ package test import ( "bytes" "fmt" + "go/ast" + "go/importer" "go/printer" + "go/types" "io/ioutil" "testing" @@ -26,15 +29,26 @@ func Mutator(t *testing.T, m mutator.Mutator, testFile string, count int) { f, fset, err := mutesting.ParseSource(originalFile) assert.Nil(t, err) + conf := types.Config{ + Importer: importer.Default(), + } + + info := &types.Info{ + Uses: make(map[*ast.Ident]types.Object), + } + + pkg, err := conf.Check(".", fset, []*ast.File{f}, info) + assert.Nil(t, err) + // Mutate a non relevant node - assert.Nil(t, m(f)) + assert.Nil(t, m(pkg, info, f)) // Count the actual mutations - n := mutesting.CountWalk(f, m) + n := mutesting.CountWalk(pkg, info, f, m) assert.Equal(t, count, n) // Mutate all relevant nodes -> test whole mutation process - changed := mutesting.MutateWalk(f, m) + changed := mutesting.MutateWalk(pkg, info, f, m) for i := 0; i < count; i++ { assert.True(t, <-changed) diff --git a/testdata/statement/remove.go b/testdata/statement/remove.go index aa02253..bd26b31 100644 --- a/testdata/statement/remove.go +++ b/testdata/statement/remove.go @@ -2,7 +2,10 @@ package example -import "fmt" +import ( + "fmt" + "net/http" +) func foo() int { n := 1 @@ -50,3 +53,20 @@ func foo() int { func bar() int { return 4 } + +func statementRemoveStructInitialization() (a http.Header, b error) { + var err error + + a, b = http.Header{}, err + + return +} + +func statementRemoveStringArrayMap() map[string][]string { + hash := "ok" + var hdr = make(map[string][]string) + + hdr["Hash"] = []string{hash} + + return hdr +} diff --git a/testdata/statement/remove.go.0.go b/testdata/statement/remove.go.0.go index 65ba347..2eada2f 100644 --- a/testdata/statement/remove.go.0.go +++ b/testdata/statement/remove.go.0.go @@ -2,7 +2,10 @@ package example -import "fmt" +import ( + "fmt" + "net/http" +) func foo() int { n := 1 @@ -49,3 +52,20 @@ func foo() int { func bar() int { return 4 } + +func statementRemoveStructInitialization() (a http.Header, b error) { + var err error + + a, b = http.Header{}, err + + return +} + +func statementRemoveStringArrayMap() map[string][]string { + hash := "ok" + var hdr = make(map[string][]string) + + hdr["Hash"] = []string{hash} + + return hdr +} diff --git a/testdata/statement/remove.go.1.go b/testdata/statement/remove.go.1.go index 5f7fed7..0734932 100644 --- a/testdata/statement/remove.go.1.go +++ b/testdata/statement/remove.go.1.go @@ -2,7 +2,10 @@ package example -import "fmt" +import ( + "fmt" + "net/http" +) func foo() int { n := 1 @@ -24,7 +27,7 @@ func foo() int { } n++ - _, _ = n, bar + _ = n bar() bar() @@ -49,3 +52,20 @@ func foo() int { func bar() int { return 4 } + +func statementRemoveStructInitialization() (a http.Header, b error) { + var err error + + a, b = http.Header{}, err + + return +} + +func statementRemoveStringArrayMap() map[string][]string { + hash := "ok" + var hdr = make(map[string][]string) + + hdr["Hash"] = []string{hash} + + return hdr +} diff --git a/testdata/statement/remove.go.10.go b/testdata/statement/remove.go.10.go index 14427e9..0f08d0c 100644 --- a/testdata/statement/remove.go.10.go +++ b/testdata/statement/remove.go.10.go @@ -2,7 +2,10 @@ package example -import "fmt" +import ( + "fmt" + "net/http" +) func foo() int { n := 1 @@ -50,3 +53,20 @@ func foo() int { func bar() int { return 4 } + +func statementRemoveStructInitialization() (a http.Header, b error) { + var err error + + a, b = http.Header{}, err + + return +} + +func statementRemoveStringArrayMap() map[string][]string { + hash := "ok" + var hdr = make(map[string][]string) + + hdr["Hash"] = []string{hash} + + return hdr +} diff --git a/testdata/statement/remove.go.11.go b/testdata/statement/remove.go.11.go index d09e940..3c35b08 100644 --- a/testdata/statement/remove.go.11.go +++ b/testdata/statement/remove.go.11.go @@ -2,7 +2,10 @@ package example -import "fmt" +import ( + "fmt" + "net/http" +) func foo() int { n := 1 @@ -50,3 +53,20 @@ func foo() int { func bar() int { return 4 } + +func statementRemoveStructInitialization() (a http.Header, b error) { + var err error + + a, b = http.Header{}, err + + return +} + +func statementRemoveStringArrayMap() map[string][]string { + hash := "ok" + var hdr = make(map[string][]string) + + hdr["Hash"] = []string{hash} + + return hdr +} diff --git a/testdata/statement/remove.go.12.go b/testdata/statement/remove.go.12.go index 44046a6..afc65c6 100644 --- a/testdata/statement/remove.go.12.go +++ b/testdata/statement/remove.go.12.go @@ -2,7 +2,10 @@ package example -import "fmt" +import ( + "fmt" + "net/http" +) func foo() int { n := 1 @@ -50,3 +53,20 @@ func foo() int { func bar() int { return 4 } + +func statementRemoveStructInitialization() (a http.Header, b error) { + var err error + + a, b = http.Header{}, err + + return +} + +func statementRemoveStringArrayMap() map[string][]string { + hash := "ok" + var hdr = make(map[string][]string) + + hdr["Hash"] = []string{hash} + + return hdr +} diff --git a/testdata/statement/remove.go.13.go b/testdata/statement/remove.go.13.go index 3779148..5ac9088 100644 --- a/testdata/statement/remove.go.13.go +++ b/testdata/statement/remove.go.13.go @@ -2,7 +2,10 @@ package example -import "fmt" +import ( + "fmt" + "net/http" +) func foo() int { n := 1 @@ -50,3 +53,20 @@ func foo() int { func bar() int { return 4 } + +func statementRemoveStructInitialization() (a http.Header, b error) { + var err error + + a, b = http.Header{}, err + + return +} + +func statementRemoveStringArrayMap() map[string][]string { + hash := "ok" + var hdr = make(map[string][]string) + + hdr["Hash"] = []string{hash} + + return hdr +} diff --git a/testdata/statement/remove.go.14.go b/testdata/statement/remove.go.14.go index cf304fe..3923640 100644 --- a/testdata/statement/remove.go.14.go +++ b/testdata/statement/remove.go.14.go @@ -2,7 +2,10 @@ package example -import "fmt" +import ( + "fmt" + "net/http" +) func foo() int { n := 1 @@ -50,3 +53,20 @@ func foo() int { func bar() int { return 4 } + +func statementRemoveStructInitialization() (a http.Header, b error) { + var err error + + a, b = http.Header{}, err + + return +} + +func statementRemoveStringArrayMap() map[string][]string { + hash := "ok" + var hdr = make(map[string][]string) + + hdr["Hash"] = []string{hash} + + return hdr +} diff --git a/testdata/statement/remove.go.15.go b/testdata/statement/remove.go.15.go new file mode 100644 index 0000000..87cfe11 --- /dev/null +++ b/testdata/statement/remove.go.15.go @@ -0,0 +1,71 @@ +// +build example-main + +package example + +import ( + "fmt" + "net/http" +) + +func foo() int { + n := 1 + + for i := 0; i < 3; i++ { + if i == 0 { + n++ + } else if i == 1 { + n += 2 + } else { + n += 3 + } + + n++ + } + + if n < 0 { + n = 0 + } + + n++ + + n += bar() + + bar() + bar() + + switch { + case n < 20: + n++ + case n > 20: + n-- + default: + n = 0 + fmt.Println(n) + func() {}() + } + + var x = 0 + x++ + + return n +} + +func bar() int { + return 4 +} + +func statementRemoveStructInitialization() (a http.Header, b error) { + var err error + _, _, _, _ = a, b, http.Header{}, err + + return +} + +func statementRemoveStringArrayMap() map[string][]string { + hash := "ok" + var hdr = make(map[string][]string) + + hdr["Hash"] = []string{hash} + + return hdr +} diff --git a/testdata/statement/remove.go.16.go b/testdata/statement/remove.go.16.go new file mode 100644 index 0000000..437b904 --- /dev/null +++ b/testdata/statement/remove.go.16.go @@ -0,0 +1,71 @@ +// +build example-main + +package example + +import ( + "fmt" + "net/http" +) + +func foo() int { + n := 1 + + for i := 0; i < 3; i++ { + if i == 0 { + n++ + } else if i == 1 { + n += 2 + } else { + n += 3 + } + + n++ + } + + if n < 0 { + n = 0 + } + + n++ + + n += bar() + + bar() + bar() + + switch { + case n < 20: + n++ + case n > 20: + n-- + default: + n = 0 + fmt.Println(n) + func() {}() + } + + var x = 0 + x++ + + return n +} + +func bar() int { + return 4 +} + +func statementRemoveStructInitialization() (a http.Header, b error) { + var err error + + a, b = http.Header{}, err + + return +} + +func statementRemoveStringArrayMap() map[string][]string { + hash := "ok" + var hdr = make(map[string][]string) + _, _ = hdr, hash + + return hdr +} diff --git a/testdata/statement/remove.go.2.go b/testdata/statement/remove.go.2.go index 41e6691..f44686b 100644 --- a/testdata/statement/remove.go.2.go +++ b/testdata/statement/remove.go.2.go @@ -2,7 +2,10 @@ package example -import "fmt" +import ( + "fmt" + "net/http" +) func foo() int { n := 1 @@ -26,7 +29,7 @@ func foo() int { n++ n += bar() - _ = bar + bar() switch { @@ -49,3 +52,20 @@ func foo() int { func bar() int { return 4 } + +func statementRemoveStructInitialization() (a http.Header, b error) { + var err error + + a, b = http.Header{}, err + + return +} + +func statementRemoveStringArrayMap() map[string][]string { + hash := "ok" + var hdr = make(map[string][]string) + + hdr["Hash"] = []string{hash} + + return hdr +} diff --git a/testdata/statement/remove.go.3.go b/testdata/statement/remove.go.3.go index a1226bd..f44686b 100644 --- a/testdata/statement/remove.go.3.go +++ b/testdata/statement/remove.go.3.go @@ -2,7 +2,10 @@ package example -import "fmt" +import ( + "fmt" + "net/http" +) func foo() int { n := 1 @@ -28,7 +31,6 @@ func foo() int { n += bar() bar() - _ = bar switch { case n < 20: @@ -50,3 +52,20 @@ func foo() int { func bar() int { return 4 } + +func statementRemoveStructInitialization() (a http.Header, b error) { + var err error + + a, b = http.Header{}, err + + return +} + +func statementRemoveStringArrayMap() map[string][]string { + hash := "ok" + var hdr = make(map[string][]string) + + hdr["Hash"] = []string{hash} + + return hdr +} diff --git a/testdata/statement/remove.go.4.go b/testdata/statement/remove.go.4.go index d81e4ad..d5ff2ec 100644 --- a/testdata/statement/remove.go.4.go +++ b/testdata/statement/remove.go.4.go @@ -2,7 +2,10 @@ package example -import "fmt" +import ( + "fmt" + "net/http" +) func foo() int { n := 1 @@ -50,3 +53,20 @@ func foo() int { func bar() int { return 4 } + +func statementRemoveStructInitialization() (a http.Header, b error) { + var err error + + a, b = http.Header{}, err + + return +} + +func statementRemoveStringArrayMap() map[string][]string { + hash := "ok" + var hdr = make(map[string][]string) + + hdr["Hash"] = []string{hash} + + return hdr +} diff --git a/testdata/statement/remove.go.5.go b/testdata/statement/remove.go.5.go index 90a878e..81f5c12 100644 --- a/testdata/statement/remove.go.5.go +++ b/testdata/statement/remove.go.5.go @@ -2,7 +2,10 @@ package example -import "fmt" +import ( + "fmt" + "net/http" +) func foo() int { n := 1 @@ -49,3 +52,20 @@ func foo() int { func bar() int { return 4 } + +func statementRemoveStructInitialization() (a http.Header, b error) { + var err error + + a, b = http.Header{}, err + + return +} + +func statementRemoveStringArrayMap() map[string][]string { + hash := "ok" + var hdr = make(map[string][]string) + + hdr["Hash"] = []string{hash} + + return hdr +} diff --git a/testdata/statement/remove.go.6.go b/testdata/statement/remove.go.6.go index 66ec9d8..c49e559 100644 --- a/testdata/statement/remove.go.6.go +++ b/testdata/statement/remove.go.6.go @@ -2,7 +2,10 @@ package example -import "fmt" +import ( + "fmt" + "net/http" +) func foo() int { n := 1 @@ -50,3 +53,20 @@ func foo() int { func bar() int { return 4 } + +func statementRemoveStructInitialization() (a http.Header, b error) { + var err error + + a, b = http.Header{}, err + + return +} + +func statementRemoveStringArrayMap() map[string][]string { + hash := "ok" + var hdr = make(map[string][]string) + + hdr["Hash"] = []string{hash} + + return hdr +} diff --git a/testdata/statement/remove.go.7.go b/testdata/statement/remove.go.7.go index df41145..e5aa84a 100644 --- a/testdata/statement/remove.go.7.go +++ b/testdata/statement/remove.go.7.go @@ -2,7 +2,10 @@ package example -import "fmt" +import ( + "fmt" + "net/http" +) func foo() int { n := 1 @@ -50,3 +53,20 @@ func foo() int { func bar() int { return 4 } + +func statementRemoveStructInitialization() (a http.Header, b error) { + var err error + + a, b = http.Header{}, err + + return +} + +func statementRemoveStringArrayMap() map[string][]string { + hash := "ok" + var hdr = make(map[string][]string) + + hdr["Hash"] = []string{hash} + + return hdr +} diff --git a/testdata/statement/remove.go.8.go b/testdata/statement/remove.go.8.go index aa4efff..3f9e650 100644 --- a/testdata/statement/remove.go.8.go +++ b/testdata/statement/remove.go.8.go @@ -2,7 +2,10 @@ package example -import "fmt" +import ( + "fmt" + "net/http" +) func foo() int { n := 1 @@ -50,3 +53,20 @@ func foo() int { func bar() int { return 4 } + +func statementRemoveStructInitialization() (a http.Header, b error) { + var err error + + a, b = http.Header{}, err + + return +} + +func statementRemoveStringArrayMap() map[string][]string { + hash := "ok" + var hdr = make(map[string][]string) + + hdr["Hash"] = []string{hash} + + return hdr +} diff --git a/testdata/statement/remove.go.9.go b/testdata/statement/remove.go.9.go index db97383..5a47901 100644 --- a/testdata/statement/remove.go.9.go +++ b/testdata/statement/remove.go.9.go @@ -2,7 +2,10 @@ package example -import "fmt" +import ( + "fmt" + "net/http" +) func foo() int { n := 1 @@ -50,3 +53,20 @@ func foo() int { func bar() int { return 4 } + +func statementRemoveStructInitialization() (a http.Header, b error) { + var err error + + a, b = http.Header{}, err + + return +} + +func statementRemoveStringArrayMap() map[string][]string { + hash := "ok" + var hdr = make(map[string][]string) + + hdr["Hash"] = []string{hash} + + return hdr +} diff --git a/walk.go b/walk.go index aee9ae0..0982d28 100644 --- a/walk.go +++ b/walk.go @@ -3,6 +3,7 @@ package mutesting import ( "fmt" "go/ast" + "go/types" "strings" "github.com/zimmski/go-mutesting/mutator" @@ -10,10 +11,12 @@ import ( // CountWalk returns the number of corresponding mutations for a given mutator. // It traverses the AST of the given node and calls the method Check of the given mutator for every node and sums up the returned counts. After completion of the traversal the final counter is returned. -func CountWalk(node ast.Node, m mutator.Mutator) int { +func CountWalk(pkg *types.Package, info *types.Info, node ast.Node, m mutator.Mutator) int { w := &countWalk{ count: 0, mutator: m, + pkg: pkg, + info: info, } ast.Walk(w, node) @@ -24,6 +27,8 @@ func CountWalk(node ast.Node, m mutator.Mutator) int { type countWalk struct { count int mutator mutator.Mutator + pkg *types.Package + info *types.Info } // Visit implements the Visit method of the ast.Visitor interface @@ -32,17 +37,19 @@ func (w *countWalk) Visit(node ast.Node) ast.Visitor { return w } - w.count += len(w.mutator(node)) + w.count += len(w.mutator(w.pkg, w.info, node)) return w } // MutateWalk mutates the given node with the given mutator returning a channel to control the mutation steps. // 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. -func MutateWalk(node ast.Node, m mutator.Mutator) chan bool { +func MutateWalk(pkg *types.Package, info *types.Info, node ast.Node, m mutator.Mutator) chan bool { w := &mutateWalk{ changed: make(chan bool), mutator: m, + pkg: pkg, + info: info, } go func() { @@ -57,6 +64,8 @@ func MutateWalk(node ast.Node, m mutator.Mutator) chan bool { type mutateWalk struct { changed chan bool mutator mutator.Mutator + pkg *types.Package + info *types.Info } // Visit implements the Visit method of the ast.Visitor interface @@ -65,7 +74,7 @@ func (w *mutateWalk) Visit(node ast.Node) ast.Visitor { return w } - for _, m := range w.mutator(node) { + for _, m := range w.mutator(w.pkg, w.info, node) { m.Change() w.changed <- true <-w.changed