Skip to content

Commit

Permalink
Add better reporting (#3)
Browse files Browse the repository at this point in the history
Signed-off-by: Davide Petilli <davide@petilli.me>
  • Loading branch information
k3rn31 committed Jul 21, 2022
1 parent 6aabc1e commit 7c1e9c3
Show file tree
Hide file tree
Showing 12 changed files with 336 additions and 143 deletions.
22 changes: 2 additions & 20 deletions cmd/unleash.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ package cmd
import (
"github.com/k3rn31/gremlins/coverage"
"github.com/k3rn31/gremlins/log"
"github.com/k3rn31/gremlins/mutant"
"github.com/k3rn31/gremlins/mutator"
"github.com/k3rn31/gremlins/mutator/workdir"
"github.com/k3rn31/gremlins/report"
"github.com/spf13/cobra"
"io/ioutil"
"os"
Expand Down Expand Up @@ -77,25 +77,7 @@ func newUnleashCmd() *unleashCmd {
mutator.WithBuildTags(buildTags))
results := mut.Run()

// Temporary reporting
var k int
var l int
var nc int
for _, m := range results {
if m.Status() == mutant.Killed {
k++
}
if m.Status() == mutant.Lived {
l++
}
if m.Status() == mutant.NotCovered {
nc++
}
}
log.Infoln("-----")
log.Infof("Killed: %d, Lived: %d, Not covered: %d\n", k, l, nc)
log.Infof("Real coverage: %.2f%%\n", float64(k+l)/float64(k+l+nc)*100)
log.Infof("Test efficacy: %.2f%%\n", float64(k)/float64(k+l)*100)
report.Do(results)
},
}

Expand Down
5 changes: 2 additions & 3 deletions codecov.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ coverage:
status:
project:
default:
target: 85%
target: auto
threshold: 5%
patch:
default:
target: 85%
threshold: 2%
informational: true
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
)

require (
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/mattn/go-colorable v0.1.9 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b h1:wDUNC2eKiL35DbLvsDhiblTUXHxcOPwQSCzi7xpQUN4=
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b/go.mod h1:VzxiSdG6j1pi7rwGm/xYI5RbtpBgM8sARDXlvEvxlu0=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
Expand Down
34 changes: 1 addition & 33 deletions log/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,11 @@ package log
import (
"fmt"
"github.com/fatih/color"
"github.com/k3rn31/gremlins/mutant"
"io"
"sync"
)

var (
fgRed = color.New(color.FgRed).SprintFunc()
fgGreen = color.New(color.FgGreen).SprintFunc()
fgHiBlack = color.New(color.FgHiBlack).SprintFunc()
)
var fgRed = color.New(color.FgRed).SprintFunc()

type log struct {
writer io.Writer
Expand Down Expand Up @@ -95,33 +90,6 @@ func Errorln(a any) {
instance.writeln(msg)
}

// Mutant logs a mutant.Mutant.
// It reports the mutant.Status, the mutant.Type and its position.
func Mutant(m mutant.Mutant) {
if instance == nil {
return
}
status := m.Status().String()
switch m.Status() {
case mutant.Killed, mutant.Runnable:
status = fgGreen(m.Status())
case mutant.Lived:
status = fgRed(m.Status())
case mutant.NotCovered:
status = fgHiBlack(m.Status())
}
instance.writef("%s%s %s at %s\n", padding(m.Status()), status, m.Type(), m.Position())
}

func padding(s mutant.Status) string {
var pad string
padLen := 12 - len(s.String())
for i := 0; i < padLen; i++ {
pad += " "
}
return pad
}

func (l *log) writef(f string, args ...any) {
_, _ = fmt.Fprintf(instance.writer, f, args...)
}
Expand Down
76 changes: 0 additions & 76 deletions log/log_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@ package log_test

import (
"bytes"
"github.com/google/go-cmp/cmp"
"github.com/k3rn31/gremlins/log"
"github.com/k3rn31/gremlins/mutant"
"go/token"
"testing"
)

Expand Down Expand Up @@ -106,76 +103,3 @@ func TestLogError(t *testing.T) {
}
})
}

func TestMutantLog(t *testing.T) {
out := &bytes.Buffer{}
defer out.Reset()
log.Init(out)
defer log.Reset()

m := stubMutant{mutant.Lived}
log.Mutant(m)
m = stubMutant{mutant.Killed}
log.Mutant(m)
m = stubMutant{mutant.NotCovered}
log.Mutant(m)
m = stubMutant{mutant.Runnable}
log.Mutant(m)

got := out.String()

want := "" +
" LIVED CONDITIONALS_BOUNDARY at aFolder/aFile.go:12:3\n" +
" KILLED CONDITIONALS_BOUNDARY at aFolder/aFile.go:12:3\n" +
" NOT COVERED CONDITIONALS_BOUNDARY at aFolder/aFile.go:12:3\n" +
" RUNNABLE CONDITIONALS_BOUNDARY at aFolder/aFile.go:12:3\n"

if !cmp.Equal(got, want) {
t.Errorf(cmp.Diff(got, want))
}
}

type stubMutant struct {
status mutant.Status
}

func (s stubMutant) Type() mutant.Type {
return mutant.ConditionalsBoundary
}

func (s stubMutant) SetType(_ mutant.Type) {
panic("implement me")
}

func (s stubMutant) Status() mutant.Status {
return s.status
}

func (s stubMutant) SetStatus(_ mutant.Status) {
panic("implement me")
}

func (s stubMutant) Position() token.Position {
return token.Position{
Filename: "aFolder/aFile.go",
Offset: 0,
Line: 12,
Column: 3,
}
}

func (s stubMutant) Pos() token.Pos {
return 123
}

func (s stubMutant) SetWorkdir(_ string) {
panic("implement me")
}

func (s stubMutant) Apply() error {
panic("implement me")
}

func (s stubMutant) Rollback() error {
panic("implement me")
}
2 changes: 2 additions & 0 deletions mutator/internal/mappings.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (
"go/token"
)

// TokenMutantType is the mapping from each token.Token and all the
// mutant.Type that can be applied to it.
var TokenMutantType = map[token.Token][]mutant.Type{
token.SUB: {mutant.InvertNegatives, mutant.ArithmeticBase},
token.ADD: {mutant.ArithmeticBase},
Expand Down
4 changes: 4 additions & 0 deletions mutator/internal/tokenmutant.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,22 @@ func NewTokenMutant(set *token.FileSet, file *ast.File, node *NodeToken) *TokenM
}
}

// Type returns the mutant.Type of the mutant.Mutant.
func (m *TokenMutant) Type() mutant.Type {
return m.mutantType
}

// SetType sets the mutant.Type of the mutant.Mutant.
func (m *TokenMutant) SetType(mt mutant.Type) {
m.mutantType = mt
}

// Status returns the mutant.Status of the mutant.Mutant.
func (m *TokenMutant) Status() mutant.Status {
return m.status
}

// SetStatus sets the mutant.Status of the mutant.Mutant.
func (m *TokenMutant) SetStatus(s mutant.Status) {
m.status = s
}
Expand Down
26 changes: 18 additions & 8 deletions mutator/mutator.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/k3rn31/gremlins/mutant"
"github.com/k3rn31/gremlins/mutator/internal"
"github.com/k3rn31/gremlins/mutator/workdir"
"github.com/k3rn31/gremlins/report"
"go/ast"
"go/parser"
"go/token"
Expand All @@ -31,6 +32,7 @@ import (
"os/exec"
"path/filepath"
"strings"
"time"
)

// Mutator is the "engine" that performs the mutation testing.
Expand Down Expand Up @@ -128,7 +130,8 @@ func WithApplyAndRollback(a func(m mutant.Mutant) error, r func(m mutant.Mutant)
// KILLED or LIVED depending on the result. If the tests pass, it means the
// TokenMutant survived, so it will be LIVED, if the tests fail, the TokenMutant will
// be KILLED.
func (mu Mutator) Run() []mutant.Mutant {
func (mu Mutator) Run() report.Results {
start := time.Now()
log.Infoln("Looking for mutants...")
mu.mutantStream = make(chan mutant.Mutant)
go func() {
Expand All @@ -141,8 +144,11 @@ func (mu Mutator) Run() []mutant.Mutant {
})
close(mu.mutantStream)
}()
res := mu.executeTests()
end := time.Now()
res.Elapsed = end.Sub(start)

return mu.executeTests()
return res
}

func (mu Mutator) runOnFile(fileName string, src io.Reader) {
Expand Down Expand Up @@ -182,7 +188,7 @@ func (mu Mutator) mutationStatus(pos token.Position) mutant.Status {
return status
}

func (mu Mutator) executeTests() []mutant.Mutant {
func (mu Mutator) executeTests() report.Results {
if mu.dryRun {
log.Infoln("Running in 'dry-run' mode.")
} else {
Expand All @@ -195,12 +201,12 @@ func (mu Mutator) executeTests() []mutant.Mutant {
defer cl()
_ = os.Chdir(wd)

var results []mutant.Mutant
var mutants []mutant.Mutant
for m := range mu.mutantStream {
m.SetWorkdir(wd)
if m.Status() == mutant.NotCovered || mu.dryRun {
results = append(results, m)
log.Mutant(m)
mutants = append(mutants, m)
report.Mutant(m)
continue
}
if err := mu.apply(m); err != nil {
Expand All @@ -221,8 +227,12 @@ func (mu Mutator) executeTests() []mutant.Mutant {
log.Errorf("failed to restore mutation at %s - %s\n\t%v", m.Position(), m.Status(), err)
// What should we do now?
}
log.Mutant(m)
results = append(results, m)
report.Mutant(m)
mutants = append(mutants, m)
}

results := report.Results{
Mutants: mutants,
}
return results
}
12 changes: 9 additions & 3 deletions mutator/mutator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,8 @@ func TestMutations(t *testing.T) {
filename: {Data: src},
}
mut := mutator.New(mapFS, tc.covProfile, dealerStub{}, mutator.WithDryRun(true))
got := mut.Run()
res := mut.Run()
got := res.Mutants

if tc.token == token.ILLEGAL {
if len(got) != 0 {
Expand Down Expand Up @@ -265,7 +266,8 @@ func TestSkipTestAndNonGoFiles(t *testing.T) {
"folder1/file": {Data: file},
}
mut := mutator.New(sys, nil, dealerStub{}, mutator.WithDryRun(true))
got := mut.Run()
res := mut.Run()
got := res.Mutants

if len(got) != 0 {
t.Errorf("should not receive results")
Expand Down Expand Up @@ -362,14 +364,18 @@ func TestMutatorTestExecution(t *testing.T) {
func(m mutant.Mutant) error {
return nil
}))
got := mut.Run()
res := mut.Run()
got := res.Mutants

if len(got) < 1 {
t.Fatal("no mutants received")
}
if got[0].Status() != tc.wantMutStatus {
t.Errorf("expected mutation to be %v, but got: %v", tc.wantMutStatus, got[0].Status())
}
if res.Elapsed <= 0 {
t.Errorf("expected elapsed time to be greater than zero, got %s", res.Elapsed)
}
})
}
}
Expand Down
Loading

0 comments on commit 7c1e9c3

Please # to comment.