From 204a620aa5c465cb640c87c853fc06761d1c7154 Mon Sep 17 00:00:00 2001 From: Aleksandr Asmakov Date: Fri, 26 Dec 2025 13:59:35 +0300 Subject: [PATCH 1/2] CTHL-5402: 1) Fix SkipMakeArgsFilter and tests 2) Fix SkipFileWithBuildTag regex and test --- internal/filter/skip_mutation.go | 46 +++++++-- internal/filter/skip_mutation_test.go | 98 ++++++++++++++----- internal/importing/filepath.go | 15 +-- internal/importing/filepath_test.go | 7 +- internal/importing/filepathfixtures/fifth.go | 1 + .../importing/filepathfixtures/fifth_test.go | 3 + 6 files changed, 129 insertions(+), 41 deletions(-) create mode 100644 internal/importing/filepathfixtures/fifth.go create mode 100644 internal/importing/filepathfixtures/fifth_test.go diff --git a/internal/filter/skip_mutation.go b/internal/filter/skip_mutation.go index adf8c3a..5bcdc8f 100644 --- a/internal/filter/skip_mutation.go +++ b/internal/filter/skip_mutation.go @@ -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 } @@ -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) + } + } +} diff --git a/internal/filter/skip_mutation_test.go b/internal/filter/skip_mutation_test.go index bf8dbbb..4bd2a17 100644 --- a/internal/filter/skip_mutation_test.go +++ b/internal/filter/skip_mutation_test.go @@ -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", @@ -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{"+", "*"}, }, } @@ -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") }) } } diff --git a/internal/importing/filepath.go b/internal/importing/filepath.go index 770b281..a13c60b 100644 --- a/internal/importing/filepath.go +++ b/internal/importing/filepath.go @@ -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 @@ -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 { @@ -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 } @@ -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 } diff --git a/internal/importing/filepath_test.go b/internal/importing/filepath_test.go index a42edb7..506c94b 100644 --- a/internal/importing/filepath_test.go +++ b/internal/importing/filepath_test.go @@ -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" ) @@ -192,6 +193,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"}, diff --git a/internal/importing/filepathfixtures/fifth.go b/internal/importing/filepathfixtures/fifth.go new file mode 100644 index 0000000..2800955 --- /dev/null +++ b/internal/importing/filepathfixtures/fifth.go @@ -0,0 +1 @@ +package filepathfixtures diff --git a/internal/importing/filepathfixtures/fifth_test.go b/internal/importing/filepathfixtures/fifth_test.go new file mode 100644 index 0000000..84f5a34 --- /dev/null +++ b/internal/importing/filepathfixtures/fifth_test.go @@ -0,0 +1,3 @@ +//go:build integration + +package filepathfixtures From 9da8888009a2c44133e7d70346f2c16299633c25 Mon Sep 17 00:00:00 2001 From: Aleksandr Asmakov Date: Fri, 26 Dec 2025 14:27:07 +0300 Subject: [PATCH 2/2] CTHL-5402: fix test --- internal/importing/filepath_test.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/internal/importing/filepath_test.go b/internal/importing/filepath_test.go index 506c94b..a024afa 100644 --- a/internal/importing/filepath_test.go +++ b/internal/importing/filepath_test.go @@ -30,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", @@ -44,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", @@ -52,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", @@ -87,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", @@ -95,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", @@ -106,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", @@ -118,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", @@ -158,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", }, @@ -255,6 +263,7 @@ func TestFilesWithExcludedDirs(t *testing.T) { { []string{"./filepathfixtures/..."}, []string{ + "filepathfixtures/fifth.go", "filepathfixtures/first.go", "filepathfixtures/second.go", "filepathfixtures/third.go", @@ -274,6 +283,7 @@ func TestFilesWithExcludedDirs(t *testing.T) { { []string{"./filepathfixtures"}, []string{ + "filepathfixtures/fifth.go", "filepathfixtures/first.go", "filepathfixtures/second.go", "filepathfixtures/third.go", @@ -285,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", @@ -295,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",