Skip to content

Commit

Permalink
#2 Implement basic algorithm for mutation discovery
Browse files Browse the repository at this point in the history
Signed-off-by: Davide Petilli <[email protected]>
  • Loading branch information
k3rn31 committed Jun 24, 2022
1 parent 030dc2a commit db94b2d
Show file tree
Hide file tree
Showing 11 changed files with 481 additions and 12 deletions.
12 changes: 0 additions & 12 deletions coverage/coverage.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,6 @@ import (
"os/exec"
)

// Block holds the start and end coordinates of a section of a source file
// covered by tests.
type Block struct {
StartLine int
StartCol int
EndLine int
EndCol int
}

// Profile is implemented as a map holding a slice of Block per each filename.
type Profile map[string][]Block

// Coverage is responsible for executing a Go test with coverage via the Run() method,
// then parsing the result coverage report file.
type Coverage struct {
Expand Down
48 changes: 48 additions & 0 deletions coverage/profile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2022 The Gremlins Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package coverage

import (
"go/token"
)

// Block holds the start and end coordinates of a section of a source file
// covered by tests.
type Block struct {
StartLine int
StartCol int
EndLine int
EndCol int
}

// Profile is implemented as a map holding a slice of Block per each filename.
type Profile map[string][]Block

func (p Profile) IsCovered(pos token.Position) bool {
block, ok := p[pos.Filename]
if !ok {
return false
}
for _, b := range block {
if pos.Line >= b.StartLine && pos.Line < b.EndLine {
if pos.Column >= b.StartCol && pos.Column < b.EndCol {
return true
}
}
}
return false
}
106 changes: 106 additions & 0 deletions coverage/profile_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright 2022 The Gremlins Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package coverage_test

import (
"github.com/k3rn31/gremlins/coverage"
"go/token"
"testing"
)

func TestIsCovered(t *testing.T) {
testCases := []struct {
name string
proFilename string
proStartL int
proEndL int
proStartC int
proEndC int

posFilename string
posL int
posC int

expected bool
}{
{
name: "returns true when position is covered",
proFilename: "test",
proStartL: 10,
proEndL: 11,
proStartC: 10,
proEndC: 11,
posFilename: "test",
posL: 10,
posC: 10,
expected: true,
},
{
name: "returns false when position is not covered",
proFilename: "test",
proStartL: 10,
proEndL: 11,
proStartC: 10,
proEndC: 11,
posFilename: "test",
posL: 11,
posC: 10,
expected: false,
},
{
name: "returns false when filename is not found",
proFilename: "test_pro",
proStartL: 10,
proEndL: 5,
proStartC: 10,
proEndC: 6,
posFilename: "test_pos",
posL: 10,
posC: 6,
expected: false,
},
}

for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
profile := coverage.Profile{
tc.proFilename: {
{
StartLine: tc.proStartL,
StartCol: tc.proStartC,
EndLine: tc.proEndL,
EndCol: tc.proEndC,
},
},
}

position := token.Position{
Filename: tc.posFilename,
Offset: 100,
Line: tc.posL,
Column: tc.posC,
}

got := profile.IsCovered(position)

if got != tc.expected {
t.Errorf("expected coverage to be %v, got %v", tc.expected, got)
}
})
}
}
88 changes: 88 additions & 0 deletions mutator/mutator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright 2022 The Gremlins Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package mutator

import (
"github.com/k3rn31/gremlins/coverage"
"go/ast"
"go/parser"
"go/token"
)

type Mutant struct {
MutantType MutantType
TokenType token.Token
Position token.Position
Covered bool
}

type Mutator struct {
covProfile coverage.Profile
}

func New(p coverage.Profile) *Mutator {
return &Mutator{covProfile: p}
}

func (m Mutator) RunWithFileName(fileName string) []Mutant {
var result []Mutant
set := token.NewFileSet()
file, _ := parser.ParseFile(set, fileName, nil, parser.ParseComments)
ast.Inspect(file, func(node ast.Node) bool {
switch node := node.(type) {
case *ast.BinaryExpr:
r, ok := inspectBinaryExpr(set, node)
if !ok {
return false
}
r.Covered = m.covProfile.IsCovered(r.Position)
result = append(result, r)
}
return true
})
return result
}

func inspectBinaryExpr(set *token.FileSet, be *ast.BinaryExpr) (Mutant, bool) {
switch be.Op {
case token.GTR:
return Mutant{
MutantType: CONDITIONAL_BOUNDARY,
TokenType: token.GTR,
Position: set.Position(be.OpPos),
}, true
case token.LSS:
return Mutant{
MutantType: CONDITIONAL_BOUNDARY,
TokenType: token.LSS,
Position: set.Position(be.OpPos),
}, true
case token.LEQ:
return Mutant{
MutantType: CONDITIONAL_BOUNDARY,
TokenType: token.LEQ,
Position: set.Position(be.OpPos),
}, true
case token.GEQ:
return Mutant{
MutantType: CONDITIONAL_BOUNDARY,
TokenType: token.GEQ,
Position: set.Position(be.OpPos),
}, true
}
return Mutant{}, false
}
Loading

0 comments on commit db94b2d

Please sign in to comment.