Skip to content

Commit

Permalink
Add support for basic mutations (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
k3rn31 committed Jun 27, 2022
1 parent 52e7068 commit 4cbf4a0
Show file tree
Hide file tree
Showing 22 changed files with 420 additions and 69 deletions.
105 changes: 96 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

[![Tests](https://github.com/k3rn31/gremlins/actions/workflows/ci.yml/badge.svg)](https://github.com/k3rn31/gremlins/actions/workflows/ci.yml)
[![Go Report Card](https://goreportcard.com/badge/github.com/k3rn31/gremlins)](https://goreportcard.com/report/github.com/k3rn31/gremlins)
[![Maintainability](https://api.codeclimate.com/v1/badges/970114e2c5a770987a75/maintainability)](https://codeclimate.com/github/k3rn31/gremlins/maintainability)
[![codecov](https://codecov.io/gh/k3rn31/gremlins/branch/main/graph/badge.svg?token=MICF9A6U3J)](https://codecov.io/gh/k3rn31/gremlins)

**WARNING: Gremlins is in an early stage of development, and it can be unstable or do anything at all. As of now, it
Expand Down Expand Up @@ -32,13 +33,7 @@ testing? You already know it will not be caught. In any case, Gremlins will repo

### Supported mutations

- [x] Conditionals boundary
- [ ] Increments
- [ ] Invert negatives
- [ ] Math
- [ ] Negate conditionals

#### Conditional Boundaries
#### Conditionals Boundaries

| Original | Mutated |
|----------|---------|
Expand All @@ -51,18 +46,109 @@ Example:

```go
if a > b {
// Do something
// Do something
}
```

will be changed to

```go
if a < b {
// Do something
// Do something
}
```

#### Conditionals Negation

| Original | Mutated |
|----------|---------|
| == | != |
| != | == |
| \> | \<= |
| <= | \> |
| < | \>= |
| \>= | < |

Example:

```go
if a == b {
// Do something
}
```

will be changed to

```go
if a != b {
// Do something
}
```

#### Increment Decrement

| Original | Mutated |
|----------|---------|
| ++ | -- |
| -- | ++ |

Example:

```go
func incr(i int) int
return i++
}
```

will be changed to

```go
func incr(i int) int {
return i--
}
```

#### Invert Negatives
It will invert negative numbers.

Example:

```go
func negate(i int) int {
return -i
}
```

will be changed to

```go
func negate(i int) int {
return +i
}
```

#### Arithmetic Base

| Original | Mutated |
|----------|---------|
| + | - |
| - | + |
| * | / |
| / | * |
| % | * |

Example:

```go
a := 1 + 2
```

will be changed to

```go
a := 1 - 2
```

### Current limitations

There are some limitations on how Gremlins works right now, but rest assured we'll try to make things better.
Expand All @@ -74,6 +160,7 @@ There are some limitations on how Gremlins works right now, but rest assured we'
- Gremlins doesn't support custom test commands; if you have to do anything different from `go test ./...` to run your
test suite, most probably it will not work with Gremlins.
- There is no way to implement custom mutations.
- It is not tested on Windows as of now and most probably it will not work there.

## What inspired Gremlins

Expand Down
8 changes: 4 additions & 4 deletions coverage/coverage.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ type execContext = func(name string, args ...string) *exec.Cmd

// New instantiates a Coverage element using exec.Command as execContext,
// actually running the command on the OS.
func New(workdir, path string) (*Coverage, error) {
func New(workdir, path string) (Coverage, error) {
path = strings.TrimSuffix(path, "/")
mod, err := getMod(path)
if err != nil {
return nil, err
return Coverage{}, err
}
return NewWithCmdAndPackage(exec.Command, mod, workdir, path), nil
}
Expand All @@ -65,8 +65,8 @@ func getMod(path string) (string, error) {
}

// NewWithCmdAndPackage instantiates a Coverage element given a custom execContext.
func NewWithCmdAndPackage(cmdContext execContext, mod, workdir, path string) *Coverage {
return &Coverage{
func NewWithCmdAndPackage(cmdContext execContext, mod, workdir, path string) Coverage {
return Coverage{
cmdContext: cmdContext,
workDir: workdir,
path: path + "/...",
Expand Down
59 changes: 51 additions & 8 deletions mutator/mappings.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,72 @@ type MutantType int

func (mt MutantType) String() string {
switch mt {
case ConditionalBoundary:
return "Conditional Boundary"
case ConditionalsBoundary:
return "CONDITIONALS_BOUNDARY"
case ConditionalsNegation:
return "CONDITIONALS_NEGATION"
case IncrementDecrement:
return "INCREMENT_DECREMENT"
case InvertNegatives:
return "INVERT_NEGATIVES"
case ArithmeticBase:
return "ARITHMETIC_BASE"
default:
panic("this should not happen")
}
}

const (
ConditionalBoundary MutantType = iota
ConditionalsBoundary MutantType = iota
ConditionalsNegation
IncrementDecrement
InvertNegatives
ArithmeticBase
)

var tokenMutantType = map[token.Token][]MutantType{
token.GTR: {ConditionalBoundary},
token.LSS: {ConditionalBoundary},
token.GEQ: {ConditionalBoundary},
token.LEQ: {ConditionalBoundary},
token.SUB: {InvertNegatives, ArithmeticBase},
token.ADD: {ArithmeticBase},
token.MUL: {ArithmeticBase},
token.QUO: {ArithmeticBase},
token.REM: {ArithmeticBase},
token.EQL: {ConditionalsNegation},
token.NEQ: {ConditionalsNegation},
token.GTR: {ConditionalsBoundary, ConditionalsNegation},
token.LSS: {ConditionalsBoundary, ConditionalsNegation},
token.GEQ: {ConditionalsBoundary, ConditionalsNegation},
token.LEQ: {ConditionalsBoundary, ConditionalsNegation},
token.INC: {IncrementDecrement},
token.DEC: {IncrementDecrement},
}

var mutations = map[MutantType]map[token.Token]token.Token{
ConditionalBoundary: {
ArithmeticBase: {
token.ADD: token.SUB,
token.SUB: token.ADD,
token.MUL: token.QUO,
token.QUO: token.MUL,
token.REM: token.MUL,
},
ConditionalsBoundary: {
token.GTR: token.GEQ,
token.LSS: token.LEQ,
token.GEQ: token.GTR,
token.LEQ: token.LSS,
},
ConditionalsNegation: {
token.EQL: token.NEQ,
token.NEQ: token.EQL,
token.LEQ: token.GTR,
token.GTR: token.LEQ,
token.LSS: token.GEQ,
token.GEQ: token.LSS,
},
IncrementDecrement: {
token.INC: token.DEC,
token.DEC: token.INC,
},
InvertNegatives: {
token.SUB: token.ADD,
},
}
26 changes: 23 additions & 3 deletions mutator/mappings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,29 @@ func TestMutantTypeString(t *testing.T) {
expected string
}{
{
"Conditional Boundary",
ConditionalBoundary,
"Conditional Boundary",
"CONDITIONALS_BOUNDARY",
ConditionalsBoundary,
"CONDITIONALS_BOUNDARY",
},
{
"CONDITIONALS_NEGATION",
ConditionalsNegation,
"CONDITIONALS_NEGATION",
},
{
"INCREMENT_DECREMENT",
IncrementDecrement,
"INCREMENT_DECREMENT",
},
{
"INVERT_NEGATIVES",
InvertNegatives,
"INVERT_NEGATIVES",
},
{
"ARITHMETIC_BASE",
ArithmeticBase,
"ARITHMETIC_BASE",
},
}
for _, tc := range testCases {
Expand Down
52 changes: 31 additions & 21 deletions mutator/mutator.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,16 @@ import (
"strings"
)

type MutationStatus int
type MutantStatus int

const (
NotCovered MutationStatus = iota
NotCovered MutantStatus = iota
Runnable
Lived
Killed
)

func (ms MutationStatus) String() string {
func (ms MutantStatus) String() string {
switch ms {
case NotCovered:
return "NOT COVERED"
Expand All @@ -52,11 +52,11 @@ func (ms MutationStatus) String() string {
}

type Mutant struct {
Position token.Position
MutantType MutantType
Status MutationStatus
Token token.Token
Mutation token.Token
Position token.Position
Type MutantType
Status MutantStatus
Token token.Token
Mutation token.Token
}

type Mutator struct {
Expand Down Expand Up @@ -88,43 +88,53 @@ func (mu Mutator) runOnFile(fileName string, src io.Reader) []Mutant {
file, _ := parser.ParseFile(set, fileName, src, parser.ParseComments)
ast.Inspect(file, func(node ast.Node) bool {
switch node := node.(type) {
case *ast.UnaryExpr:
r, ok := mu.mutants(set, node.Op, node.OpPos)
if !ok {
return true
}
result = append(result, r...)
case *ast.BinaryExpr:
r, ok := mu.inspectBinaryExpr(set, node)
r, ok := mu.mutants(set, node.Op, node.OpPos)
if !ok {
return true
}
result = append(result, r...)
case *ast.IncDecStmt:
r, ok := mu.mutants(set, node.Tok, node.TokPos)
if !ok {
return true
}
result = append(result, r...)
case *ast.UnaryExpr:
// Do something
}
return true
})
return result
}

func (mu Mutator) inspectBinaryExpr(set *token.FileSet, be *ast.BinaryExpr) ([]Mutant, bool) {
func (mu Mutator) mutants(set *token.FileSet, tok token.Token, tokPos token.Pos) ([]Mutant, bool) {
var result []Mutant
mutantTypes, ok := tokenMutantType[be.Op]
mutantTypes, ok := tokenMutantType[tok]
if !ok {
return nil, false
}
for _, mt := range mutantTypes {
pos := set.Position(be.OpPos)
pos := set.Position(tokPos)
mutant := Mutant{
MutantType: mt,
Token: be.Op,
Mutation: mutations[mt][be.Op],
Status: mutationStatus(mu, pos),
Position: pos,
Type: mt,
Token: tok,
Mutation: mutations[mt][tok],
Status: mu.mutationStatus(pos),
Position: pos,
}
result = append(result, mutant)
}

return result, true
}

func mutationStatus(mu Mutator, pos token.Position) MutationStatus {
var status MutationStatus
func (mu Mutator) mutationStatus(pos token.Position) MutantStatus {
var status MutantStatus
if mu.covProfile.IsCovered(pos) {
status = Runnable
}
Expand Down
Loading

0 comments on commit 4cbf4a0

Please sign in to comment.