Merge branch '16-test-coverage' into 'master'

Resolve "test coverage"

Closes #16

See merge request slon/shad-go-private!20
This commit is contained in:
verytable 2020-03-19 22:49:24 +00:00
commit 629e9b3c92
44 changed files with 1020 additions and 1 deletions

85
coverme/README.md Normal file
View file

@ -0,0 +1,85 @@
## coverme
В этой задаче нужно покрыть простой todo-app http сервис unit тестами.
Необходимо покрыть все sub-package'и.
Package main можно не тестировать.
Существующие файлы менять не нужно.
Нужно создавать новые файлы с тестами.
Тестирующая система будет проверять code coverage.
Порог задан в [coverage_test.go](./app/coverage_test.go)
Важно понимать, что coverage 100% - не решение всех проблем.
В коде по-прежнему могут быть ошибки.
Coverage 100% говорит ровно о том, что все строки кода выполнялись.
Хорошие тесты в первую очередь тестируют функциональность.
Как посмотреть coverage:
```
go test -v -cover ./coverme/...
```
Coverage можно выводить в html (см. ссылки), и эта функциональность поддерживается в Goland.
## Ссылки
1. cover: https://blog.golang.org/cover
2. [gomock](https://github.com/golang/mock) для создания мока базы данных при тестировании серевера
3. [httptest.ResponseRecorder](https://golang.org/pkg/net/http/httptest/#ResponseRecorder) для тестирования handler'ов сервера
4. [httptest.Server](https://golang.org/pkg/net/http/httptest/#Server) для тестирования клинета
5. Если вы ждёте, когда же выложат лекцию: https://www.youtube.com/watch?v=ndmB0bj7eyw
## O сервисе
Todo-app с минимальной функциональностью + client.
Запуск:
```
✗ go run ./coverme/main.go -port 6029
```
Health check:
```
✗ curl -i -X GET localhost:6029/
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 19 Mar 2020 21:46:02 GMT
Content-Length: 24
"API is up and working!"
```
Создать новое todo:
```
✗ curl -i localhost:6029/todo/create -d '{"title":"A","content":"a"}'
HTTP/1.1 201 Created
Content-Type: application/json
Date: Thu, 19 Mar 2020 21:41:31 GMT
Content-Length: 51
{"id":0,"title":"A","content":"a","finished":false}
```
Получить todo по id:
```
✗ curl -i localhost:6029/todo/0
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 19 Mar 2020 21:44:17 GMT
Content-Length: 51
{"id":0,"title":"A","content":"a","finished":false}
```
Получить все todo:
```
✗ curl -i -X GET localhost:6029/todo
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 19 Mar 2020 21:44:37 GMT
Content-Length: 53
[{"id":0,"title":"A","content":"a","finished":false}]
```

99
coverme/app/app.go Normal file
View file

@ -0,0 +1,99 @@
// +build !change
package app
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strconv"
"strings"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"gitlab.com/slon/shad-go/coverme/models"
"gitlab.com/slon/shad-go/coverme/utils"
)
type App struct {
router *mux.Router
db models.Storage
}
func New(db models.Storage) *App {
return &App{db: db}
}
func (app *App) Start(port int) {
app.initRoutes()
app.run(fmt.Sprintf(":%d", port))
}
func (app *App) initRoutes() {
app.router = mux.NewRouter()
app.router.HandleFunc("/", app.status).Methods("Get")
app.router.HandleFunc("/todo", app.list).Methods("Get")
app.router.HandleFunc("/todo/{id:[0-9]+}", app.getTodo).Methods("Get")
app.router.HandleFunc("/todo/create", app.addTodo).Methods("Post")
}
func (app *App) run(addr string) {
loggedRouter := handlers.LoggingHandler(os.Stderr, app.router)
_ = http.ListenAndServe(addr, loggedRouter)
}
func (app *App) list(w http.ResponseWriter, r *http.Request) {
todos, err := app.db.GetAll()
if err != nil {
utils.ServerError(w)
return
}
_ = utils.RespondJSON(w, http.StatusOK, todos)
}
func (app *App) addTodo(w http.ResponseWriter, r *http.Request) {
req := &models.AddRequest{}
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&req); err != nil {
utils.BadRequest(w, "payload is required")
return
}
defer func() { _ = r.Body.Close() }()
if req.Title == "" {
utils.BadRequest(w, "title is required")
return
}
todo, err := app.db.AddTodo(req.Title, req.Content)
if err != nil {
utils.ServerError(w)
return
}
_ = utils.RespondJSON(w, http.StatusCreated, todo)
}
func (app *App) getTodo(w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(strings.TrimPrefix(r.URL.Path, "/todo/"))
if err != nil {
utils.BadRequest(w, "ID must be an int")
return
}
todo, err := app.db.GetTodo(models.ID(id))
if err != nil {
utils.ServerError(w)
return
}
_ = utils.RespondJSON(w, http.StatusOK, todo)
}
func (app *App) status(w http.ResponseWriter, r *http.Request) {
_ = utils.RespondJSON(w, http.StatusOK, "API is up and working!")
}

View file

@ -0,0 +1,5 @@
// +build !change
package app
// min coverage: 90%

70
coverme/client/client.go Normal file
View file

@ -0,0 +1,70 @@
// +build !change
package client
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"gitlab.com/slon/shad-go/coverme/models"
)
type Client struct {
addr string
}
func New(addr string) *Client {
return &Client{addr: addr}
}
func (c *Client) Add(r *models.AddRequest) (*models.Todo, error) {
data, _ := json.Marshal(r)
resp, err := http.Post(c.addr+"/create", "application/json", bytes.NewReader(data))
if err != nil {
return nil, err
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusCreated {
return nil, fmt.Errorf("unexpected status code %d", resp.StatusCode)
}
var todo *models.Todo
err = json.NewDecoder(resp.Body).Decode(&todo)
return todo, err
}
func (c *Client) Get(id models.ID) (*models.Todo, error) {
resp, err := http.Get(c.addr + fmt.Sprintf("/todo/%d", id))
if err != nil {
return nil, err
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code %d", resp.StatusCode)
}
var todo *models.Todo
err = json.NewDecoder(resp.Body).Decode(&todo)
return todo, err
}
func (c *Client) List() ([]*models.Todo, error) {
resp, err := http.Get(c.addr + "/todo")
if err != nil {
return nil, err
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code %d", resp.StatusCode)
}
var todos []*models.Todo
err = json.NewDecoder(resp.Body).Decode(&todos)
return todos, err
}

18
coverme/main.go Normal file
View file

@ -0,0 +1,18 @@
// +build !change
package main
import (
"flag"
"gitlab.com/slon/shad-go/coverme/app"
"gitlab.com/slon/shad-go/coverme/models"
)
func main() {
port := flag.Int("port", 8080, "port to listen")
flag.Parse()
db := models.NewInMemoryStorage()
app.New(db).Start(*port)
}

70
coverme/models/storage.go Normal file
View file

@ -0,0 +1,70 @@
// +build !change
package models
import (
"fmt"
"sync"
)
type Storage interface {
AddTodo(string, string) (*Todo, error)
GetTodo(ID) (*Todo, error)
GetAll() ([]*Todo, error)
}
type InMemoryStorage struct {
mu sync.RWMutex
todos map[ID]*Todo
nextID ID
}
func NewInMemoryStorage() *InMemoryStorage {
return &InMemoryStorage{
todos: make(map[ID]*Todo),
}
}
func (s *InMemoryStorage) AddTodo(title, content string) (*Todo, error) {
s.mu.Lock()
defer s.mu.Unlock()
id := s.nextID
s.nextID++
todo := &Todo{
ID: id,
Title: title,
Content: content,
Finished: false,
}
s.todos[todo.ID] = todo
return todo, nil
}
func (s *InMemoryStorage) GetTodo(id ID) (*Todo, error) {
s.mu.RLock()
defer s.mu.RUnlock()
todo, ok := s.todos[id]
if !ok {
return nil, fmt.Errorf("todo %d not found", id)
}
return todo, nil
}
func (s *InMemoryStorage) GetAll() ([]*Todo, error) {
s.mu.RLock()
defer s.mu.RUnlock()
var out []*Todo
for _, todo := range s.todos {
out = append(out, todo)
}
return out, nil
}

25
coverme/models/todo.go Normal file
View file

@ -0,0 +1,25 @@
// +build !change
package models
type ID int
type AddRequest struct {
Title string `json:"title"`
Content string `json:"content"`
}
type Todo struct {
ID ID `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
Finished bool `json:"finished"`
}
func (t *Todo) MarkFinished() {
t.Finished = true
}
func (t *Todo) MarkUnfished() {
t.Finished = false
}

View file

@ -0,0 +1,32 @@
// +build !change
package utils
import (
"encoding/json"
"net/http"
)
func RespondJSON(w http.ResponseWriter, status int, data interface{}) error {
response, err := json.Marshal(data)
if err != nil {
return err
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_, _ = w.Write(response)
return nil
}
func ServerError(w http.ResponseWriter) {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte("Server encountered an error."))
}
func BadRequest(w http.ResponseWriter, message string) {
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte(message))
}

2
go.mod
View file

@ -6,6 +6,8 @@ require (
github.com/go-resty/resty/v2 v2.1.0 github.com/go-resty/resty/v2 v2.1.0
github.com/gofrs/uuid v3.2.0+incompatible github.com/gofrs/uuid v3.2.0+incompatible
github.com/golang/mock v1.4.1 github.com/golang/mock v1.4.1
github.com/gorilla/handlers v1.4.2
github.com/gorilla/mux v1.7.4
github.com/spf13/cobra v0.0.5 github.com/spf13/cobra v0.0.5
github.com/stretchr/testify v1.4.0 github.com/stretchr/testify v1.4.0
go.uber.org/goleak v1.0.0 go.uber.org/goleak v1.0.0

4
go.sum
View file

@ -26,6 +26,10 @@ github.com/gonum/internal v0.0.0-20181124074243-f884aa714029/go.mod h1:Pu4dmpkhS
github.com/gonum/lapack v0.0.0-20181123203213-e4cdc5a0bff9/go.mod h1:XA3DeT6rxh2EAE789SSiSJNqxPaC0aE9J8NTOI0Jo/A= github.com/gonum/lapack v0.0.0-20181123203213-e4cdc5a0bff9/go.mod h1:XA3DeT6rxh2EAE789SSiSJNqxPaC0aE9J8NTOI0Jo/A=
github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9/go.mod h1:0EXg4mc1CNP0HCqCz+K4ts155PXIlUywf0wqN+GfPZw= github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9/go.mod h1:0EXg4mc1CNP0HCqCz+K4ts155PXIlUywf0wqN+GfPZw=
github.com/googleapis/gax-go v0.0.0-20161107002406-da06d194a00e/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go v0.0.0-20161107002406-da06d194a00e/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg=
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=

View file

@ -0,0 +1,96 @@
package commands
import (
"fmt"
"go/parser"
"go/token"
"strconv"
"strings"
"golang.org/x/tools/cover"
)
// coverageCommentPrefix is a prefix of coverage comment.
//
// Coverage comment has the following form:
//
// // min coverage: 80.5%
const coverageCommentPrefix = "min coverage: "
type CoverageRequirements struct {
Enabled bool
Percent float64
}
// getCoverageRequirements searches for comment in test files
// that specifies test coverage requirements.
//
// Stops on first matching comment.
func getCoverageRequirements(rootPackage string) *CoverageRequirements {
files := listTestFiles(rootPackage)
for _, f := range files {
if r, _ := searchCoverageComment(f); r.Enabled {
return r
}
}
return &CoverageRequirements{}
}
// searchCoverageComment searches for the first occurrence of the comment of the form
//
// // min coverage: 80.5%
//
// Stops on the first matching comment.
func searchCoverageComment(fname string) (*CoverageRequirements, error) {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, fname, nil, parser.ParseComments)
if err != nil {
return nil, err
}
for _, c := range f.Comments {
t := c.Text()
if !strings.HasPrefix(t, coverageCommentPrefix) || !strings.HasSuffix(t, "%\n") {
continue
}
t = strings.TrimPrefix(t, coverageCommentPrefix)
t = strings.TrimSuffix(t, "%\n")
percent, err := strconv.ParseFloat(t, 64)
if err != nil {
continue
}
if percent < 0 || percent > 100.0 {
continue
}
return &CoverageRequirements{Enabled: true, Percent: percent}, nil
}
return &CoverageRequirements{}, nil
}
// calCoverage calculates coverage percent for given coverage profile.
func calCoverage(profile string) (float64, error) {
profiles, err := cover.ParseProfiles(profile)
if err != nil {
return 0.0, fmt.Errorf("cannot parse coverage profile file %s: %w", profile, err)
}
var total, covered int
for _, p := range profiles {
for _, block := range p.Blocks {
total += block.NumStmt
if block.Count > 0 {
covered += block.NumStmt
}
}
}
if total == 0 {
return 0.0, nil
}
return float64(covered) / float64(total) * 100, nil
}

View file

@ -0,0 +1,13 @@
package commands
import (
"testing"
"github.com/stretchr/testify/require"
)
func Test_getCoverageRequirements(t *testing.T) {
r := getCoverageRequirements("../testdata/coverage/sum")
require.True(t, r.Enabled)
require.Equal(t, 90.0, r.Percent)
}

View file

@ -242,12 +242,21 @@ func runTests(testDir, privateRepo, problem string) error {
} }
} }
coverageReq := getCoverageRequirements(privateRepo)
if coverageReq.Enabled {
log.Printf("required coverage: %.2f%%", coverageReq.Percent)
}
binariesJSON, _ := json.Marshal(binaries) binariesJSON, _ := json.Marshal(binaries)
for testPkg := range testPkgs { for testPkg := range testPkgs {
binPath := filepath.Join(binCache, randomName()) binPath := filepath.Join(binCache, randomName())
testBinaries[testPkg] = binPath testBinaries[testPkg] = binPath
if err := runGo("test", "-mod", "readonly", "-tags", "private", "-c", "-o", binPath, testPkg); err != nil { cmd := []string{"test", "-mod", "readonly", "-tags", "private", "-c", "-o", binPath, testPkg}
if coverageReq.Enabled {
cmd = append(cmd, "-cover")
}
if err := runGo(cmd...); err != nil {
return fmt.Errorf("error building test in %s: %w", testPkg, err) return fmt.Errorf("error building test in %s: %w", testPkg, err)
} }
} }
@ -257,6 +266,9 @@ func runTests(testDir, privateRepo, problem string) error {
{ {
cmd := exec.Command(testBinary) cmd := exec.Command(testBinary)
if coverageReq.Enabled {
cmd = exec.Command(testBinary, "-test.coverprofile", "c.out")
}
if currentUserIsRoot() { if currentUserIsRoot() {
if err := sandbox(cmd); err != nil { if err := sandbox(cmd); err != nil {
log.Fatal(err) log.Fatal(err)
@ -273,6 +285,20 @@ func runTests(testDir, privateRepo, problem string) error {
} }
} }
if coverageReq.Enabled {
log.Printf("checking coverage is at least %.2f%% for %s", coverageReq.Percent, testPkg)
percent, err := calCoverage(filepath.Join(testDir, relPath, "c.out"))
if err != nil {
return err
}
if percent < coverageReq.Percent {
return fmt.Errorf("poor coverage %.2f%%; expected at least %.2f%%",
percent, coverageReq.Percent)
}
}
{ {
benchCmd := exec.Command(testBinary, "-test.bench=.", "-test.run=^$") benchCmd := exec.Command(testBinary, "-test.bench=.", "-test.run=^$")
if currentUserIsRoot() { if currentUserIsRoot() {

View file

@ -0,0 +1,22 @@
// This is subpkg package comment.
package subpkg
// Incorrect coverage comments:
// min coverage: -1%
// min coverage: 100.001%
// min coverage: 100 %
// min coverage:10%
// min coverage: 19%
// Correct coverage comment:
// min coverage: 90%
// Testtool uses first matching comment.
// min coverage: 91%

View file

@ -0,0 +1,2 @@
// This is package comment.
package sum

View file

@ -0,0 +1,8 @@
package sum
// This is a single line comment.
/*
This is multiline
comment!
*/

View file

@ -0,0 +1,77 @@
# options for analysis running
run:
# default concurrency is a available CPU number
concurrency: 8
# timeout for analysis, e.g. 30s, 5m, default is 1m
deadline: 5m
# exit code when at least one issue was found, default is 1
issues-exit-code: 1
# include test files or not, default is true
tests: true
# output configuration options
output:
# colored-line-number|line-number|json|tab|checkstyle, default is "colored-line-number"
format: colored-line-number
# print lines of code with issue, default is true
print-issued-lines: true
# print linter name in the end of issue text, default is true
print-linter-name: true
# all available settings of specific linters
linters-settings:
govet:
# report about shadowed variables
check-shadowing: true
golint:
# minimal confidence for issues, default is 0.8
min-confidence: 0.8
gofmt:
# simplify code: gofmt with `-s` option, true by default
simplify: true
goimports:
# put imports beginning with prefix after 3rd-party packages;
# it's a comma-separated list of prefixes
local-prefixes: gitlab.com
linters:
disable-all: true
enable:
- errcheck
- gofmt
- golint
- gosimple
- govet
- ineffassign
- scopelint
- staticcheck
- typecheck
- unconvert
issues:
# List of regexps of issue texts to exclude, empty list by default.
# But independently from this option we use default exclude patterns,
# it can be disabled by `exclude-use-default: false`. To list all
# excluded by default patterns execute `golangci-lint run --help`
exclude:
- Using the variable on range scope .* in function literal
# Independently from option `exclude` we use default exclude patterns,
# it can be disabled by this option. To list all
# excluded by default patterns execute `golangci-lint run --help`.
# Default value for this option is true.
exclude-use-default: true
# Maximum issues count per one linter. Set to 0 to disable. Default is 50.
max-per-linter: 0
# Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
max-same-issues: 0

View file

@ -0,0 +1,17 @@
// +build !change
package coverme_test
import (
"testing"
"github.com/stretchr/testify/require"
"gitlab.com/slon/shad-go/coverme"
)
// min coverage: 70%
func TestSum(t *testing.T) {
require.Equal(t, int64(2), coverme.Sum(1, 1))
}

View file

@ -0,0 +1,14 @@
// +build !change
package coverme
import "net/http"
func Sum(a, b int64) int64 {
if a == 0 {
return b
} else if a == http.StatusOK {
return http.StatusCreated + b - 1
}
return a + b
}

View file

@ -0,0 +1,15 @@
// +build solution
package coverme
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestSum(t *testing.T) {
require.Equal(t, int64(2), Sum(1, 1))
require.Equal(t, int64(1), Sum(0, 1))
require.Equal(t, int64(202), Sum(200, 2))
}

View file

@ -0,0 +1,12 @@
// +build !change
package subpkg
func AddOne(n int) int {
if n == 0 {
return 1
} else if n == 1 {
return 2
}
return n + 1
}

View file

@ -0,0 +1,13 @@
package subpkg
import (
"testing"
"github.com/stretchr/testify/require"
)
// min coverage: 100%
func TestAddOne(t *testing.T) {
require.Equal(t, 1, AddOne(0))
}

View file

@ -0,0 +1,5 @@
package subpkg
func AddTwo(n int) int {
return AddOne(n + 1)
}

View file

@ -0,0 +1,5 @@
module gitlab.com/slon/shad-go
go 1.13
require github.com/stretchr/testify v1.5.1

View file

@ -0,0 +1,11 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View file

@ -0,0 +1,11 @@
package coverme
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestSum2(t *testing.T) {
require.Equal(t, int64(202), Sum(200, 2))
}

View file

@ -0,0 +1,17 @@
// +build !change
package coverme_test
import (
"testing"
"github.com/stretchr/testify/require"
"gitlab.com/slon/shad-go/coverme"
)
// min coverage: 70%
func TestSum(t *testing.T) {
require.Equal(t, int64(2), coverme.Sum(1, 1))
}

View file

@ -0,0 +1,14 @@
// +build !change
package coverme
import "net/http"
func Sum(a, b int64) int64 {
if a == 0 {
return b
} else if a == http.StatusOK {
return http.StatusCreated + b - 1
}
return a + b
}

View file

@ -0,0 +1,16 @@
package subpkg
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestAddOne_2(t *testing.T) {
require.Equal(t, 2, AddOne(1))
require.Equal(t, 30, AddOne(29))
}
func TestAddTwo(t *testing.T) {
require.Equal(t, 4, AddTwo(2))
}

View file

@ -0,0 +1,12 @@
// +build !change
package subpkg
func AddOne(n int) int {
if n == 0 {
return 1
} else if n == 1 {
return 2
}
return n + 1
}

View file

@ -0,0 +1,13 @@
package subpkg
import (
"testing"
"github.com/stretchr/testify/require"
)
// min coverage: 100%
func TestAddOne(t *testing.T) {
require.Equal(t, 1, AddOne(0))
}

View file

@ -0,0 +1,5 @@
package subpkg
func AddTwo(n int) int {
return AddOne(n + 1)
}

View file

@ -0,0 +1,5 @@
module gitlab.com/slon/shad-go
go 1.13
require github.com/stretchr/testify v1.5.1

View file

@ -0,0 +1,10 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View file

@ -0,0 +1,77 @@
# options for analysis running
run:
# default concurrency is a available CPU number
concurrency: 8
# timeout for analysis, e.g. 30s, 5m, default is 1m
deadline: 5m
# exit code when at least one issue was found, default is 1
issues-exit-code: 1
# include test files or not, default is true
tests: true
# output configuration options
output:
# colored-line-number|line-number|json|tab|checkstyle, default is "colored-line-number"
format: colored-line-number
# print lines of code with issue, default is true
print-issued-lines: true
# print linter name in the end of issue text, default is true
print-linter-name: true
# all available settings of specific linters
linters-settings:
govet:
# report about shadowed variables
check-shadowing: true
golint:
# minimal confidence for issues, default is 0.8
min-confidence: 0.8
gofmt:
# simplify code: gofmt with `-s` option, true by default
simplify: true
goimports:
# put imports beginning with prefix after 3rd-party packages;
# it's a comma-separated list of prefixes
local-prefixes: gitlab.com
linters:
disable-all: true
enable:
- errcheck
- gofmt
- golint
- gosimple
- govet
- ineffassign
- scopelint
- staticcheck
- typecheck
- unconvert
issues:
# List of regexps of issue texts to exclude, empty list by default.
# But independently from this option we use default exclude patterns,
# it can be disabled by `exclude-use-default: false`. To list all
# excluded by default patterns execute `golangci-lint run --help`
exclude:
- Using the variable on range scope .* in function literal
# Independently from option `exclude` we use default exclude patterns,
# it can be disabled by this option. To list all
# excluded by default patterns execute `golangci-lint run --help`.
# Default value for this option is true.
exclude-use-default: true
# Maximum issues count per one linter. Set to 0 to disable. Default is 50.
max-per-linter: 0
# Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
max-same-issues: 0

View file

@ -0,0 +1,5 @@
module gitlab.com/slon/shad-go
go 1.13
require github.com/stretchr/testify v1.5.1

View file

@ -0,0 +1,11 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View file

@ -0,0 +1,16 @@
// +build !change
package poorcoverage_test
import (
"testing"
"github.com/stretchr/testify/require"
"gitlab.com/slon/shad-go/poorcoverage"
)
// min coverage: 100%
func TestSum(t *testing.T) {
require.Equal(t, int64(2), poorcoverage.Sum(1, 1))
}

View file

@ -0,0 +1,10 @@
// +build !change
package poorcoverage
func Sum(a, b int64) int64 {
if a == 0 {
return b
}
return a + b
}

View file

@ -0,0 +1,14 @@
// +build solution
package poorcoverage
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestSum(t *testing.T) {
require.Equal(t, int64(2), Sum(1, 1))
require.Equal(t, int64(1), Sum(0, 1))
}

View file

@ -0,0 +1,5 @@
module gitlab.com/slon/shad-go
go 1.13
require github.com/stretchr/testify v1.5.1

View file

@ -0,0 +1,6 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View file

@ -0,0 +1,16 @@
// +build !change
package poorcoverage_test
import (
"testing"
"github.com/stretchr/testify/require"
"gitlab.com/slon/shad-go/poorcoverage"
)
// min coverage: 100%
func TestSum(t *testing.T) {
require.Equal(t, int64(2), poorcoverage.Sum(1, 1))
}

View file

@ -0,0 +1,10 @@
// +build !change
package poorcoverage
func Sum(a, b int64) int64 {
if a == 0 {
return b
}
return a + b
}