diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml new file mode 100644 index 0000000..9d901e2 --- /dev/null +++ b/.github/workflows/mutation.yml @@ -0,0 +1,41 @@ +name: Mutation Testing + +on: + push: + branches: [master] + +permissions: + contents: read + +jobs: + mutation: + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5 + with: + go-version-file: go.mod + + - name: Build go-mutesting + run: go build -o /usr/local/bin/go-mutesting ./cmd/go-mutesting + + - name: Run mutation testing + # Scoped to packages with isolated unit tests. + # Excluded: + # cmd/ - integration tests re-invoke the binary (would recurse) + # internal/annotation - build errors prevent compilation + # internal/importing - relies on GOPATH/src layout; fails in module mode + # internal/parser - uses deprecated go/loader; excluded until migration + # The tool always exits 0 regardless of mutation score, so this step is + # informational. Check report.json for the full breakdown. + run: | + go-mutesting \ + --exec-timeout 30 \ + github.com/avito-tech/go-mutesting/mutator/arithmetic \ + github.com/avito-tech/go-mutesting/mutator/branch \ + github.com/avito-tech/go-mutesting/mutator/expression \ + github.com/avito-tech/go-mutesting/mutator/loop \ + github.com/avito-tech/go-mutesting/mutator/numbers \ + github.com/avito-tech/go-mutesting/mutator/statement \ + github.com/avito-tech/go-mutesting/internal/filter diff --git a/.gitignore b/.gitignore index daf913b..f4d1ba6 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,9 @@ _testmain.go *.exe *.test *.prof + +# go-mutesting build output and run artifacts +/go-mutesting +report.json +report.html +*.go.new diff --git a/internal/filter/skip_mutation_test.go b/internal/filter/skip_mutation_test.go index 4bd2a17..e6676ee 100644 --- a/internal/filter/skip_mutation_test.go +++ b/internal/filter/skip_mutation_test.go @@ -87,6 +87,32 @@ func TestSkipMutationForInitSlicesAndMaps(t *testing.T) { expectedLiterals: []string{"3", "2", "4"}, expectedOperators: []string{"+", "*"}, }, + { + // make(MySlice) with one arg is syntactically valid for the parser even though + // the type-checker would reject it; it exercises the len(Args) > 1 guard. + name: "do not skip mutation for single-arg make with type alias", + code: `package main; type MySlice []int; var a = make(MySlice)`, + expectedLiterals: []string{}, + expectedOperators: []string{}, + }, + { + name: "skip mutation for unary negation of complex inner expression", + code: `package main; var a = make([]int, 0, -(2+3))`, + expectedLiterals: []string{"0", "2", "3"}, + expectedOperators: []string{"+"}, + }, + { + name: "skip mutation for nested call with multiple int args", + code: `package main; var a = make([]int, someFunc(2, 3))`, + expectedLiterals: []string{"2", "3"}, + expectedOperators: []string{}, + }, + { + name: "do not skip mutation for unary on float literals", + code: `package main; var a = make([]float64, -1.5, +2.3)`, + expectedLiterals: []string{}, + expectedOperators: []string{}, + }, } for _, tt := range tests {