Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 39 additions & 7 deletions internal/filter/skip_mutation.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,12 @@ func (s *SkipMakeArgsFilter) Collect(file *ast.File, _ *token.FileSet, _ string)
arg0 := callExpr.Args[0]
_, isArray := arg0.(*ast.ArrayType)
_, isMap := arg0.(*ast.MapType)
if isArray || isMap {
if lit, ok := callExpr.Args[1].(*ast.BasicLit); ok && lit.Kind == token.INT {
s.IgnoredNodes[lit.Pos()] = callExpr
}
_, isIdent := arg0.(*ast.Ident)
if isArray || isMap || isIdent {
s.collectForIgnoredNodes(callExpr.Args[1], callExpr)

if len(callExpr.Args) > 2 {
if lit, ok := callExpr.Args[2].(*ast.BasicLit); ok && lit.Kind == token.INT {
s.IgnoredNodes[lit.Pos()] = callExpr
}
s.collectForIgnoredNodes(callExpr.Args[2], callExpr)
}
return false
}
Expand All @@ -47,3 +45,37 @@ func (s *SkipMakeArgsFilter) ShouldSkip(node ast.Node, _ string) bool {
_, exists := s.IgnoredNodes[node.Pos()]
return exists
}

// collectForIgnoredNodes recursively collects all numeric literals and unary/binary operators in an expression
func (s *SkipMakeArgsFilter) collectForIgnoredNodes(expr ast.Expr, callExpr *ast.CallExpr) {
switch e := expr.(type) {
case *ast.BasicLit:
// Direct numeric literal
if e.Kind == token.INT {
s.IgnoredNodes[e.Pos()] = callExpr
}
case *ast.BinaryExpr:
// Binary operations (addition, subtraction, multiplication, division, etc.)
s.IgnoredNodes[e.OpPos] = callExpr
s.collectForIgnoredNodes(e.X, callExpr)
s.collectForIgnoredNodes(e.Y, callExpr)
case *ast.CallExpr:
// Calling a function (e.g. len())
for _, arg := range e.Args {
s.collectForIgnoredNodes(arg, callExpr)
}
case *ast.ParenExpr:
// Expression in brackets
s.collectForIgnoredNodes(e.X, callExpr)
case *ast.UnaryExpr:
// Unary operators (+, -, etc.)
if xLit, ok := e.X.(*ast.BasicLit); ok && xLit.Kind == token.INT {
// If a unary operator is applied to a numeric literal, both the operator and the literal itself are added.
s.IgnoredNodes[e.OpPos] = callExpr
s.IgnoredNodes[xLit.Pos()] = callExpr
} else {
// Otherwise, we continue the tour inside.
s.collectForIgnoredNodes(e.X, callExpr)
}
}
}
98 changes: 72 additions & 26 deletions internal/filter/skip_mutation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,22 @@ import (

func TestSkipMutationForInitSlicesAndMaps(t *testing.T) {
tests := []struct {
name string
code string
expected bool
name string
code string
expectedLiterals []string
expectedOperators []string
}{
{
name: "skip mutation for slice init with len",
code: `package main; var a = make([]int, 10)`,
expected: true,
name: "skip mutation for slice init with len",
code: `package main; var a = make([]int, 10)`,
expectedLiterals: []string{"10"},
expectedOperators: []string{},
},
{
name: "skip mutation for slice init with len and cap",
code: `package main; var a = make([]int, 10, 20)`,
expected: true,
name: "skip mutation for slice init with len and cap",
code: `package main; var a = make([]int, 10, 20)`,
expectedLiterals: []string{"10", "20"},
expectedOperators: []string{},
},
{
name: "skip mutation for slice init in assigning inside a struct",
Expand All @@ -39,22 +42,50 @@ func TestSkipMutationForInitSlicesAndMaps(t *testing.T) {
func fff() {
testCase := &TestCase{ Devices: make([]DeviceStatus, 0) }
}`,
expected: true,
expectedLiterals: []string{"0"},
expectedOperators: []string{},
},
{
name: "skip mutation for map init with cap",
code: `package main; var a = make(map[int]bool, 0)`,
expected: true,
name: "skip mutation for map init with cap",
code: `package main; var a = make(map[int]bool, 0)`,
expectedLiterals: []string{"0"},
expectedOperators: []string{},
},
{
name: "do not skip mutation for slice init with variable",
code: `package main; var x = 10; var a = make([]int, x)`,
expected: false,
name: "do not skip mutation for slice init with variable",
code: `package main; var x = 10; var a = make([]int, x)`,
expectedLiterals: []string{},
expectedOperators: []string{},
},
{
name: "do not skip mutation for other literals",
code: `package main; var a = 42`,
expected: false,
name: "do not skip mutation for other literals",
code: `package main; var a = 42`,
expectedLiterals: []string{},
expectedOperators: []string{},
},
{
name: "skip mutation for slice with type alias",
code: `package main; type Contents []*string; var a = make(Contents, 5)`,
expectedLiterals: []string{"5"},
expectedOperators: []string{},
},
{
name: "skip mutation for complex expression with binary op",
code: `package main; var a = make([]int, 0, len(arr)+1)`,
expectedLiterals: []string{"0", "1"},
expectedOperators: []string{"+"},
},
{
name: "skip mutation for unary operations",
code: `package main; var a = make([]int, -5, +10)`,
expectedLiterals: []string{"5", "10"},
expectedOperators: []string{"-", "+"},
},
{
name: "skip mutation for complex nested expressions",
code: `package main; var a = make([]int, (3), len(arr)+2*4)`,
expectedLiterals: []string{"3", "2", "4"},
expectedOperators: []string{"+", "*"},
},
}

Expand All @@ -67,20 +98,35 @@ func TestSkipMutationForInitSlicesAndMaps(t *testing.T) {
}

s := NewSkipMakeArgsFilter()
s.Collect(node, nil, "")
s.ShouldSkip(node, "")
s.Collect(node, fs, "")

var foundLiterals []string
var foundOperators []string

var result bool
ast.Inspect(node, func(n ast.Node) bool {
if lit, ok := n.(*ast.BasicLit); ok && lit.Kind == token.INT {
_, found := s.IgnoredNodes[lit.Pos()]
result = found
return false
if _, exists := s.IgnoredNodes[lit.Pos()]; exists {
foundLiterals = append(foundLiterals, lit.Value)
}
}

if binExpr, ok := n.(*ast.BinaryExpr); ok {
if _, exists := s.IgnoredNodes[binExpr.OpPos]; exists {
foundOperators = append(foundOperators, binExpr.Op.String())
}
}

if unaryExpr, ok := n.(*ast.UnaryExpr); ok {
if _, exists := s.IgnoredNodes[unaryExpr.OpPos]; exists {
foundOperators = append(foundOperators, unaryExpr.Op.String())
}
}

return true
})

assert.Equal(t, tt.expected, result)
assert.ElementsMatch(t, tt.expectedLiterals, foundLiterals, "Literals mismatch")
assert.ElementsMatch(t, tt.expectedOperators, foundOperators, "Operators mismatch")
})
}
}
15 changes: 8 additions & 7 deletions internal/importing/filepath.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ import (
"github.com/avito-tech/go-mutesting/internal/models"
)

var (
buildTagRegex = regexp.MustCompile(`(?m)^//(go:build|\s*\+build)`)
testFileSuffix = "_test.go"
)

func packagesWithFilesOfArgs(args []string, opts *models.Options) map[string]map[string]struct{} {
var filenames []string

Expand All @@ -48,10 +53,6 @@ func packagesWithFilesOfArgs(args []string, opts *models.Options) map[string]map

fileLookup := make(map[string]struct{})
pkgs := make(map[string]map[string]struct{})
var re *regexp.Regexp
if opts.Config.SkipFileWithBuildTag {
re = regexp.MustCompile("\\+build (.*)(\\s+)package") //nolint:gosimple
}

for _, filename := range filenames {
if _, ok := fileLookup[filename]; ok {
Expand All @@ -72,7 +73,7 @@ func packagesWithFilesOfArgs(args []string, opts *models.Options) map[string]map
}
}

if strings.HasSuffix(filename, "_test.go") { // ignore test files
if strings.HasSuffix(filename, testFileSuffix) { // ignore test files
continue
}

Expand All @@ -82,13 +83,13 @@ func packagesWithFilesOfArgs(args []string, opts *models.Options) map[string]map
continue
}

testName := filename[:nameSize-3] + "_test.go"
testName := filename[:nameSize-3] + testFileSuffix
if !exists(testName) {
continue
}

if opts.Config.SkipFileWithBuildTag { // ignore files with test with build tags
isBuildTag := regexpSearchInFile(testName, re)
isBuildTag := regexpSearchInFile(testName, buildTagRegex)
if isBuildTag {
continue
}
Expand Down
23 changes: 20 additions & 3 deletions internal/importing/filepath_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package importing

import (
"fmt"
"github.com/avito-tech/go-mutesting/internal/models"
"os"
"testing"

"github.com/avito-tech/go-mutesting/internal/models"

"github.com/stretchr/testify/assert"
)

Expand All @@ -29,11 +30,12 @@ func TestFilesOfArgs(t *testing.T) {
// directories
{
[]string{"./filepathfixtures"},
[]string{"filepathfixtures/first.go", "filepathfixtures/second.go", "filepathfixtures/third.go"},
[]string{"filepathfixtures/fifth.go", "filepathfixtures/first.go", "filepathfixtures/second.go", "filepathfixtures/third.go"},
},
{
[]string{"../importing/filepathfixtures"},
[]string{
"../importing/filepathfixtures/fifth.go",
"../importing/filepathfixtures/first.go",
"../importing/filepathfixtures/second.go",
"../importing/filepathfixtures/third.go",
Expand All @@ -43,6 +45,7 @@ func TestFilesOfArgs(t *testing.T) {
{
[]string{"github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures"},
[]string{
p + "github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures/fifth.go",
p + "github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures/first.go",
p + "github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures/second.go",
p + "github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures/third.go",
Expand All @@ -51,6 +54,7 @@ func TestFilesOfArgs(t *testing.T) {
{
[]string{"github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures/..."},
[]string{
p + "github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures/fifth.go",
p + "github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures/first.go",
p + "github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures/second.go",
p + "github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures/third.go",
Expand Down Expand Up @@ -86,6 +90,7 @@ func TestPackagesWithFilesOfArgs(t *testing.T) {
{
[]string{"./filepathfixtures"},
[]Package{{Name: "filepathfixtures", Files: []string{
"filepathfixtures/fifth.go",
"filepathfixtures/first.go",
"filepathfixtures/second.go",
"filepathfixtures/third.go",
Expand All @@ -94,6 +99,7 @@ func TestPackagesWithFilesOfArgs(t *testing.T) {
{
[]string{"../importing/filepathfixtures"},
[]Package{{Name: "../importing/filepathfixtures", Files: []string{
"../importing/filepathfixtures/fifth.go",
"../importing/filepathfixtures/first.go",
"../importing/filepathfixtures/second.go",
"../importing/filepathfixtures/third.go",
Expand All @@ -105,6 +111,7 @@ func TestPackagesWithFilesOfArgs(t *testing.T) {
[]Package{{
Name: p + "github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures",
Files: []string{
p + "github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures/fifth.go",
p + "github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures/first.go",
p + "github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures/second.go",
p + "github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures/third.go",
Expand All @@ -117,6 +124,7 @@ func TestPackagesWithFilesOfArgs(t *testing.T) {
{
Name: p + "github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures",
Files: []string{
p + "github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures/fifth.go",
p + "github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures/first.go",
p + "github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures/second.go",
p + "github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures/third.go",
Expand Down Expand Up @@ -157,12 +165,13 @@ func TestFilesWithSkipWithoutTests(t *testing.T) {
// directories
{
[]string{"./filepathfixtures"},
[]string{"filepathfixtures/second.go", "filepathfixtures/third.go"},
[]string{"filepathfixtures/fifth.go", "filepathfixtures/second.go", "filepathfixtures/third.go"},
},
// packages
{
[]string{"github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures/..."},
[]string{
p + "github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures/fifth.go",
p + "github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures/second.go",
p + "github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures/third.go",
},
Expand Down Expand Up @@ -192,6 +201,10 @@ func TestFilesWithSkipWithBuildTagsTests(t *testing.T) {
[]string{"./filepathfixtures/third.go"},
[]string(nil),
},
{
[]string{"./filepathfixtures/fifth.go"},
[]string(nil),
},
{
[]string{"./filepathfixtures/second.go"},
[]string{"./filepathfixtures/second.go"},
Expand Down Expand Up @@ -250,6 +263,7 @@ func TestFilesWithExcludedDirs(t *testing.T) {
{
[]string{"./filepathfixtures/..."},
[]string{
"filepathfixtures/fifth.go",
"filepathfixtures/first.go",
"filepathfixtures/second.go",
"filepathfixtures/third.go",
Expand All @@ -269,6 +283,7 @@ func TestFilesWithExcludedDirs(t *testing.T) {
{
[]string{"./filepathfixtures"},
[]string{
"filepathfixtures/fifth.go",
"filepathfixtures/first.go",
"filepathfixtures/second.go",
"filepathfixtures/third.go",
Expand All @@ -280,6 +295,7 @@ func TestFilesWithExcludedDirs(t *testing.T) {
{
[]string{"github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures/..."},
[]string{
p + "github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures/fifth.go",
p + "github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures/first.go",
p + "github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures/second.go",
p + "github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures/third.go",
Expand All @@ -290,6 +306,7 @@ func TestFilesWithExcludedDirs(t *testing.T) {
{
[]string{"github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures/..."},
[]string{
p + "github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures/fifth.go",
p + "github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures/first.go",
p + "github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures/second.go",
p + "github.com/avito-tech/go-mutesting/internal/importing/filepathfixtures/third.go",
Expand Down
1 change: 1 addition & 0 deletions internal/importing/filepathfixtures/fifth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package filepathfixtures
3 changes: 3 additions & 0 deletions internal/importing/filepathfixtures/fifth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
//go:build integration

package filepathfixtures