From cd7f4d69f18a001201139a8751ac957e116bcb1d Mon Sep 17 00:00:00 2001 From: Arseny Balobanov Date: Thu, 19 Mar 2020 01:19:44 +0300 Subject: [PATCH 01/11] Support test coverage requirements via comment. --- tools/testtool/commands/test_submission.go | 28 ++++++- .../coverage/sum/subpkg/empty_test.go | 22 ++++++ tools/testtool/testdata/coverage/sum/sum.go | 2 + .../testdata/coverage/sum/sum_test.go | 8 ++ .../correct/coverme/private/.golangci.yml | 77 +++++++++++++++++++ .../coverme/private/coverme/coverage_test.go | 16 ++++ .../coverme/private/coverme/service.go | 10 +++ .../coverme/private/coverme/solution_test.go | 14 ++++ .../coverme/private/coverme/subpkg/f.go | 12 +++ .../coverme/private/coverme/subpkg/f_test.go | 13 ++++ .../correct/coverme/private/go.mod | 5 ++ .../correct/coverme/private/go.sum | 11 +++ .../coverme/student/coverme/coverage_test.go | 16 ++++ .../coverme/student/coverme/service.go | 10 +++ .../student/coverme/subpkg/additional_test.go | 12 +++ .../coverme/student/coverme/subpkg/f.go | 12 +++ .../coverme/student/coverme/subpkg/f_test.go | 13 ++++ .../correct/coverme/student/go.mod | 5 ++ .../correct/coverme/student/go.sum | 6 ++ .../poorcoverage/private/.golangci.yml | 77 +++++++++++++++++++ .../incorrect/poorcoverage/private/go.mod | 5 ++ .../incorrect/poorcoverage/private/go.sum | 11 +++ .../private/poorcoverage/coverage_test.go | 16 ++++ .../private/poorcoverage/service.go | 10 +++ .../private/poorcoverage/solution_test.go | 14 ++++ .../incorrect/poorcoverage/student/go.mod | 5 ++ .../incorrect/poorcoverage/student/go.sum | 6 ++ .../student/poorcoverage/coverage_test.go | 16 ++++ .../student/poorcoverage/service.go | 10 +++ 29 files changed, 461 insertions(+), 1 deletion(-) create mode 100644 tools/testtool/testdata/coverage/sum/subpkg/empty_test.go create mode 100644 tools/testtool/testdata/coverage/sum/sum.go create mode 100644 tools/testtool/testdata/coverage/sum/sum_test.go create mode 100644 tools/testtool/testdata/submissions/correct/coverme/private/.golangci.yml create mode 100644 tools/testtool/testdata/submissions/correct/coverme/private/coverme/coverage_test.go create mode 100644 tools/testtool/testdata/submissions/correct/coverme/private/coverme/service.go create mode 100644 tools/testtool/testdata/submissions/correct/coverme/private/coverme/solution_test.go create mode 100644 tools/testtool/testdata/submissions/correct/coverme/private/coverme/subpkg/f.go create mode 100644 tools/testtool/testdata/submissions/correct/coverme/private/coverme/subpkg/f_test.go create mode 100644 tools/testtool/testdata/submissions/correct/coverme/private/go.mod create mode 100644 tools/testtool/testdata/submissions/correct/coverme/private/go.sum create mode 100644 tools/testtool/testdata/submissions/correct/coverme/student/coverme/coverage_test.go create mode 100644 tools/testtool/testdata/submissions/correct/coverme/student/coverme/service.go create mode 100644 tools/testtool/testdata/submissions/correct/coverme/student/coverme/subpkg/additional_test.go create mode 100644 tools/testtool/testdata/submissions/correct/coverme/student/coverme/subpkg/f.go create mode 100644 tools/testtool/testdata/submissions/correct/coverme/student/coverme/subpkg/f_test.go create mode 100644 tools/testtool/testdata/submissions/correct/coverme/student/go.mod create mode 100644 tools/testtool/testdata/submissions/correct/coverme/student/go.sum create mode 100644 tools/testtool/testdata/submissions/incorrect/poorcoverage/private/.golangci.yml create mode 100644 tools/testtool/testdata/submissions/incorrect/poorcoverage/private/go.mod create mode 100644 tools/testtool/testdata/submissions/incorrect/poorcoverage/private/go.sum create mode 100644 tools/testtool/testdata/submissions/incorrect/poorcoverage/private/poorcoverage/coverage_test.go create mode 100644 tools/testtool/testdata/submissions/incorrect/poorcoverage/private/poorcoverage/service.go create mode 100644 tools/testtool/testdata/submissions/incorrect/poorcoverage/private/poorcoverage/solution_test.go create mode 100644 tools/testtool/testdata/submissions/incorrect/poorcoverage/student/go.mod create mode 100644 tools/testtool/testdata/submissions/incorrect/poorcoverage/student/go.sum create mode 100644 tools/testtool/testdata/submissions/incorrect/poorcoverage/student/poorcoverage/coverage_test.go create mode 100644 tools/testtool/testdata/submissions/incorrect/poorcoverage/student/poorcoverage/service.go diff --git a/tools/testtool/commands/test_submission.go b/tools/testtool/commands/test_submission.go index bb9a166..cc31b5a 100644 --- a/tools/testtool/commands/test_submission.go +++ b/tools/testtool/commands/test_submission.go @@ -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) for testPkg := range testPkgs { binPath := filepath.Join(binCache, randomName()) 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) } } @@ -257,6 +266,9 @@ func runTests(testDir, privateRepo, problem string) error { { cmd := exec.Command(testBinary) + if coverageReq.Enabled { + cmd = exec.Command(testBinary, "-test.coverprofile", "c.out") + } if currentUserIsRoot() { if err := sandbox(cmd); err != nil { 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=^$") if currentUserIsRoot() { diff --git a/tools/testtool/testdata/coverage/sum/subpkg/empty_test.go b/tools/testtool/testdata/coverage/sum/subpkg/empty_test.go new file mode 100644 index 0000000..78fb9a3 --- /dev/null +++ b/tools/testtool/testdata/coverage/sum/subpkg/empty_test.go @@ -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% diff --git a/tools/testtool/testdata/coverage/sum/sum.go b/tools/testtool/testdata/coverage/sum/sum.go new file mode 100644 index 0000000..ac21192 --- /dev/null +++ b/tools/testtool/testdata/coverage/sum/sum.go @@ -0,0 +1,2 @@ +// This is package comment. +package sum diff --git a/tools/testtool/testdata/coverage/sum/sum_test.go b/tools/testtool/testdata/coverage/sum/sum_test.go new file mode 100644 index 0000000..6a389e2 --- /dev/null +++ b/tools/testtool/testdata/coverage/sum/sum_test.go @@ -0,0 +1,8 @@ +package sum + +// This is a single line comment. + +/* +This is multiline +comment! +*/ diff --git a/tools/testtool/testdata/submissions/correct/coverme/private/.golangci.yml b/tools/testtool/testdata/submissions/correct/coverme/private/.golangci.yml new file mode 100644 index 0000000..4b4d4be --- /dev/null +++ b/tools/testtool/testdata/submissions/correct/coverme/private/.golangci.yml @@ -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 diff --git a/tools/testtool/testdata/submissions/correct/coverme/private/coverme/coverage_test.go b/tools/testtool/testdata/submissions/correct/coverme/private/coverme/coverage_test.go new file mode 100644 index 0000000..0b1f59f --- /dev/null +++ b/tools/testtool/testdata/submissions/correct/coverme/private/coverme/coverage_test.go @@ -0,0 +1,16 @@ +// +build !change + +package coverme_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + "gitlab.com/slon/shad-go/coverme" +) + +// min coverage: 60% + +func TestSum(t *testing.T) { + require.Equal(t, int64(2), coverme.Sum(1, 1)) +} diff --git a/tools/testtool/testdata/submissions/correct/coverme/private/coverme/service.go b/tools/testtool/testdata/submissions/correct/coverme/private/coverme/service.go new file mode 100644 index 0000000..d350073 --- /dev/null +++ b/tools/testtool/testdata/submissions/correct/coverme/private/coverme/service.go @@ -0,0 +1,10 @@ +// +build !change + +package coverme + +func Sum(a, b int64) int64 { + if a == 0 { + return b + } + return a + b +} diff --git a/tools/testtool/testdata/submissions/correct/coverme/private/coverme/solution_test.go b/tools/testtool/testdata/submissions/correct/coverme/private/coverme/solution_test.go new file mode 100644 index 0000000..6d72392 --- /dev/null +++ b/tools/testtool/testdata/submissions/correct/coverme/private/coverme/solution_test.go @@ -0,0 +1,14 @@ +// +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)) +} diff --git a/tools/testtool/testdata/submissions/correct/coverme/private/coverme/subpkg/f.go b/tools/testtool/testdata/submissions/correct/coverme/private/coverme/subpkg/f.go new file mode 100644 index 0000000..cb0417a --- /dev/null +++ b/tools/testtool/testdata/submissions/correct/coverme/private/coverme/subpkg/f.go @@ -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 +} diff --git a/tools/testtool/testdata/submissions/correct/coverme/private/coverme/subpkg/f_test.go b/tools/testtool/testdata/submissions/correct/coverme/private/coverme/subpkg/f_test.go new file mode 100644 index 0000000..5f90a7b --- /dev/null +++ b/tools/testtool/testdata/submissions/correct/coverme/private/coverme/subpkg/f_test.go @@ -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)) +} diff --git a/tools/testtool/testdata/submissions/correct/coverme/private/go.mod b/tools/testtool/testdata/submissions/correct/coverme/private/go.mod new file mode 100644 index 0000000..a8be277 --- /dev/null +++ b/tools/testtool/testdata/submissions/correct/coverme/private/go.mod @@ -0,0 +1,5 @@ +module gitlab.com/slon/shad-go + +go 1.13 + +require github.com/stretchr/testify v1.5.1 diff --git a/tools/testtool/testdata/submissions/correct/coverme/private/go.sum b/tools/testtool/testdata/submissions/correct/coverme/private/go.sum new file mode 100644 index 0000000..331fa69 --- /dev/null +++ b/tools/testtool/testdata/submissions/correct/coverme/private/go.sum @@ -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= diff --git a/tools/testtool/testdata/submissions/correct/coverme/student/coverme/coverage_test.go b/tools/testtool/testdata/submissions/correct/coverme/student/coverme/coverage_test.go new file mode 100644 index 0000000..0b1f59f --- /dev/null +++ b/tools/testtool/testdata/submissions/correct/coverme/student/coverme/coverage_test.go @@ -0,0 +1,16 @@ +// +build !change + +package coverme_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + "gitlab.com/slon/shad-go/coverme" +) + +// min coverage: 60% + +func TestSum(t *testing.T) { + require.Equal(t, int64(2), coverme.Sum(1, 1)) +} diff --git a/tools/testtool/testdata/submissions/correct/coverme/student/coverme/service.go b/tools/testtool/testdata/submissions/correct/coverme/student/coverme/service.go new file mode 100644 index 0000000..d350073 --- /dev/null +++ b/tools/testtool/testdata/submissions/correct/coverme/student/coverme/service.go @@ -0,0 +1,10 @@ +// +build !change + +package coverme + +func Sum(a, b int64) int64 { + if a == 0 { + return b + } + return a + b +} diff --git a/tools/testtool/testdata/submissions/correct/coverme/student/coverme/subpkg/additional_test.go b/tools/testtool/testdata/submissions/correct/coverme/student/coverme/subpkg/additional_test.go new file mode 100644 index 0000000..b624221 --- /dev/null +++ b/tools/testtool/testdata/submissions/correct/coverme/student/coverme/subpkg/additional_test.go @@ -0,0 +1,12 @@ +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)) +} diff --git a/tools/testtool/testdata/submissions/correct/coverme/student/coverme/subpkg/f.go b/tools/testtool/testdata/submissions/correct/coverme/student/coverme/subpkg/f.go new file mode 100644 index 0000000..cb0417a --- /dev/null +++ b/tools/testtool/testdata/submissions/correct/coverme/student/coverme/subpkg/f.go @@ -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 +} diff --git a/tools/testtool/testdata/submissions/correct/coverme/student/coverme/subpkg/f_test.go b/tools/testtool/testdata/submissions/correct/coverme/student/coverme/subpkg/f_test.go new file mode 100644 index 0000000..5f90a7b --- /dev/null +++ b/tools/testtool/testdata/submissions/correct/coverme/student/coverme/subpkg/f_test.go @@ -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)) +} diff --git a/tools/testtool/testdata/submissions/correct/coverme/student/go.mod b/tools/testtool/testdata/submissions/correct/coverme/student/go.mod new file mode 100644 index 0000000..a8be277 --- /dev/null +++ b/tools/testtool/testdata/submissions/correct/coverme/student/go.mod @@ -0,0 +1,5 @@ +module gitlab.com/slon/shad-go + +go 1.13 + +require github.com/stretchr/testify v1.5.1 diff --git a/tools/testtool/testdata/submissions/correct/coverme/student/go.sum b/tools/testtool/testdata/submissions/correct/coverme/student/go.sum new file mode 100644 index 0000000..fb52f53 --- /dev/null +++ b/tools/testtool/testdata/submissions/correct/coverme/student/go.sum @@ -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= diff --git a/tools/testtool/testdata/submissions/incorrect/poorcoverage/private/.golangci.yml b/tools/testtool/testdata/submissions/incorrect/poorcoverage/private/.golangci.yml new file mode 100644 index 0000000..4b4d4be --- /dev/null +++ b/tools/testtool/testdata/submissions/incorrect/poorcoverage/private/.golangci.yml @@ -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 diff --git a/tools/testtool/testdata/submissions/incorrect/poorcoverage/private/go.mod b/tools/testtool/testdata/submissions/incorrect/poorcoverage/private/go.mod new file mode 100644 index 0000000..a8be277 --- /dev/null +++ b/tools/testtool/testdata/submissions/incorrect/poorcoverage/private/go.mod @@ -0,0 +1,5 @@ +module gitlab.com/slon/shad-go + +go 1.13 + +require github.com/stretchr/testify v1.5.1 diff --git a/tools/testtool/testdata/submissions/incorrect/poorcoverage/private/go.sum b/tools/testtool/testdata/submissions/incorrect/poorcoverage/private/go.sum new file mode 100644 index 0000000..331fa69 --- /dev/null +++ b/tools/testtool/testdata/submissions/incorrect/poorcoverage/private/go.sum @@ -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= diff --git a/tools/testtool/testdata/submissions/incorrect/poorcoverage/private/poorcoverage/coverage_test.go b/tools/testtool/testdata/submissions/incorrect/poorcoverage/private/poorcoverage/coverage_test.go new file mode 100644 index 0000000..23d2167 --- /dev/null +++ b/tools/testtool/testdata/submissions/incorrect/poorcoverage/private/poorcoverage/coverage_test.go @@ -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)) +} diff --git a/tools/testtool/testdata/submissions/incorrect/poorcoverage/private/poorcoverage/service.go b/tools/testtool/testdata/submissions/incorrect/poorcoverage/private/poorcoverage/service.go new file mode 100644 index 0000000..d7e2cad --- /dev/null +++ b/tools/testtool/testdata/submissions/incorrect/poorcoverage/private/poorcoverage/service.go @@ -0,0 +1,10 @@ +// +build !change + +package poorcoverage + +func Sum(a, b int64) int64 { + if a == 0 { + return b + } + return a + b +} diff --git a/tools/testtool/testdata/submissions/incorrect/poorcoverage/private/poorcoverage/solution_test.go b/tools/testtool/testdata/submissions/incorrect/poorcoverage/private/poorcoverage/solution_test.go new file mode 100644 index 0000000..00ab41c --- /dev/null +++ b/tools/testtool/testdata/submissions/incorrect/poorcoverage/private/poorcoverage/solution_test.go @@ -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)) +} diff --git a/tools/testtool/testdata/submissions/incorrect/poorcoverage/student/go.mod b/tools/testtool/testdata/submissions/incorrect/poorcoverage/student/go.mod new file mode 100644 index 0000000..a8be277 --- /dev/null +++ b/tools/testtool/testdata/submissions/incorrect/poorcoverage/student/go.mod @@ -0,0 +1,5 @@ +module gitlab.com/slon/shad-go + +go 1.13 + +require github.com/stretchr/testify v1.5.1 diff --git a/tools/testtool/testdata/submissions/incorrect/poorcoverage/student/go.sum b/tools/testtool/testdata/submissions/incorrect/poorcoverage/student/go.sum new file mode 100644 index 0000000..fb52f53 --- /dev/null +++ b/tools/testtool/testdata/submissions/incorrect/poorcoverage/student/go.sum @@ -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= diff --git a/tools/testtool/testdata/submissions/incorrect/poorcoverage/student/poorcoverage/coverage_test.go b/tools/testtool/testdata/submissions/incorrect/poorcoverage/student/poorcoverage/coverage_test.go new file mode 100644 index 0000000..23d2167 --- /dev/null +++ b/tools/testtool/testdata/submissions/incorrect/poorcoverage/student/poorcoverage/coverage_test.go @@ -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)) +} diff --git a/tools/testtool/testdata/submissions/incorrect/poorcoverage/student/poorcoverage/service.go b/tools/testtool/testdata/submissions/incorrect/poorcoverage/student/poorcoverage/service.go new file mode 100644 index 0000000..d7e2cad --- /dev/null +++ b/tools/testtool/testdata/submissions/incorrect/poorcoverage/student/poorcoverage/service.go @@ -0,0 +1,10 @@ +// +build !change + +package poorcoverage + +func Sum(a, b int64) int64 { + if a == 0 { + return b + } + return a + b +} From bb0c91683f23cf7cf18ffa0a3c66ea6a2e61ff2c Mon Sep 17 00:00:00 2001 From: Arseny Balobanov Date: Thu, 19 Mar 2020 01:29:11 +0300 Subject: [PATCH 02/11] Adding forgotten coverage helpers. --- tools/testtool/commands/coverage.go | 98 ++++++++++++++++++++++++ tools/testtool/commands/coverage_test.go | 17 ++++ 2 files changed, 115 insertions(+) create mode 100644 tools/testtool/commands/coverage.go create mode 100644 tools/testtool/commands/coverage_test.go diff --git a/tools/testtool/commands/coverage.go b/tools/testtool/commands/coverage.go new file mode 100644 index 0000000..1f2f538 --- /dev/null +++ b/tools/testtool/commands/coverage.go @@ -0,0 +1,98 @@ +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) + + r := &CoverageRequirements{} + for _, f := range files { + r, _ := searchCoverageComment(f) + if r.Enabled { + return r + } + } + + return r +} + +// 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 +} diff --git a/tools/testtool/commands/coverage_test.go b/tools/testtool/commands/coverage_test.go new file mode 100644 index 0000000..766eae3 --- /dev/null +++ b/tools/testtool/commands/coverage_test.go @@ -0,0 +1,17 @@ +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) + t.Logf("r: %+v", r) + //require.Equal(t, + // absPaths([]string{"sum/private_test.go", "sum/public_test.go"}), + // getCoverageRequirements("../testdata/coverage")) +} From 8c9f5151b1b278e3e6b1beb9af12e2d5d00ed39d Mon Sep 17 00:00:00 2001 From: Arseny Balobanov Date: Thu, 19 Mar 2020 01:33:16 +0300 Subject: [PATCH 03/11] Fix variable shadowing pointed out by linter. --- tools/testtool/commands/coverage.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tools/testtool/commands/coverage.go b/tools/testtool/commands/coverage.go index 1f2f538..fa35ed9 100644 --- a/tools/testtool/commands/coverage.go +++ b/tools/testtool/commands/coverage.go @@ -29,15 +29,13 @@ type CoverageRequirements struct { func getCoverageRequirements(rootPackage string) *CoverageRequirements { files := listTestFiles(rootPackage) - r := &CoverageRequirements{} for _, f := range files { - r, _ := searchCoverageComment(f) - if r.Enabled { + if r, _ := searchCoverageComment(f); r.Enabled { return r } } - return r + return &CoverageRequirements{} } // searchCoverageComment searches for the first occurrence of the comment of the form From 9848183cc15673f869b9599dd863fbc921d21e33 Mon Sep 17 00:00:00 2001 From: Arseny Balobanov Date: Thu, 19 Mar 2020 01:40:57 +0300 Subject: [PATCH 04/11] Cleanup comments. --- tools/testtool/commands/coverage_test.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tools/testtool/commands/coverage_test.go b/tools/testtool/commands/coverage_test.go index 766eae3..c978296 100644 --- a/tools/testtool/commands/coverage_test.go +++ b/tools/testtool/commands/coverage_test.go @@ -10,8 +10,4 @@ func Test_getCoverageRequirements(t *testing.T) { r := getCoverageRequirements("../testdata/coverage/sum") require.True(t, r.Enabled) require.Equal(t, 90.0, r.Percent) - t.Logf("r: %+v", r) - //require.Equal(t, - // absPaths([]string{"sum/private_test.go", "sum/public_test.go"}), - // getCoverageRequirements("../testdata/coverage")) } From a9ca8a5c7cb8001eaaafc619ee96d30eca13a557 Mon Sep 17 00:00:00 2001 From: Arseny Balobanov Date: Thu, 19 Mar 2020 02:38:45 +0300 Subject: [PATCH 05/11] Test that testtool won't check coverage of imported package e.g. net/http. --- .../correct/coverme/private/coverme/coverage_test.go | 3 ++- .../correct/coverme/private/coverme/service.go | 4 ++++ .../correct/coverme/private/coverme/solution_test.go | 1 + .../correct/coverme/private/coverme/subpkg/g.go | 5 +++++ .../coverme/student/coverme/additional_test.go | 11 +++++++++++ .../correct/coverme/student/coverme/coverage_test.go | 3 ++- .../correct/coverme/student/coverme/service.go | 4 ++++ .../coverme/student/coverme/subpkg/additional_test.go | 4 ++++ .../correct/coverme/student/coverme/subpkg/g.go | 5 +++++ .../submissions/correct/coverme/student/go.sum | 4 ++++ 10 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 tools/testtool/testdata/submissions/correct/coverme/private/coverme/subpkg/g.go create mode 100644 tools/testtool/testdata/submissions/correct/coverme/student/coverme/additional_test.go create mode 100644 tools/testtool/testdata/submissions/correct/coverme/student/coverme/subpkg/g.go diff --git a/tools/testtool/testdata/submissions/correct/coverme/private/coverme/coverage_test.go b/tools/testtool/testdata/submissions/correct/coverme/private/coverme/coverage_test.go index 0b1f59f..6aebc0d 100644 --- a/tools/testtool/testdata/submissions/correct/coverme/private/coverme/coverage_test.go +++ b/tools/testtool/testdata/submissions/correct/coverme/private/coverme/coverage_test.go @@ -6,10 +6,11 @@ import ( "testing" "github.com/stretchr/testify/require" + "gitlab.com/slon/shad-go/coverme" ) -// min coverage: 60% +// min coverage: 70% func TestSum(t *testing.T) { require.Equal(t, int64(2), coverme.Sum(1, 1)) diff --git a/tools/testtool/testdata/submissions/correct/coverme/private/coverme/service.go b/tools/testtool/testdata/submissions/correct/coverme/private/coverme/service.go index d350073..48e8e89 100644 --- a/tools/testtool/testdata/submissions/correct/coverme/private/coverme/service.go +++ b/tools/testtool/testdata/submissions/correct/coverme/private/coverme/service.go @@ -2,9 +2,13 @@ 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 } diff --git a/tools/testtool/testdata/submissions/correct/coverme/private/coverme/solution_test.go b/tools/testtool/testdata/submissions/correct/coverme/private/coverme/solution_test.go index 6d72392..059a2b5 100644 --- a/tools/testtool/testdata/submissions/correct/coverme/private/coverme/solution_test.go +++ b/tools/testtool/testdata/submissions/correct/coverme/private/coverme/solution_test.go @@ -11,4 +11,5 @@ import ( 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)) } diff --git a/tools/testtool/testdata/submissions/correct/coverme/private/coverme/subpkg/g.go b/tools/testtool/testdata/submissions/correct/coverme/private/coverme/subpkg/g.go new file mode 100644 index 0000000..acbff5b --- /dev/null +++ b/tools/testtool/testdata/submissions/correct/coverme/private/coverme/subpkg/g.go @@ -0,0 +1,5 @@ +package subpkg + +func AddTwo(n int) int { + return AddOne(n + 1) +} diff --git a/tools/testtool/testdata/submissions/correct/coverme/student/coverme/additional_test.go b/tools/testtool/testdata/submissions/correct/coverme/student/coverme/additional_test.go new file mode 100644 index 0000000..95dc7a3 --- /dev/null +++ b/tools/testtool/testdata/submissions/correct/coverme/student/coverme/additional_test.go @@ -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)) +} diff --git a/tools/testtool/testdata/submissions/correct/coverme/student/coverme/coverage_test.go b/tools/testtool/testdata/submissions/correct/coverme/student/coverme/coverage_test.go index 0b1f59f..6aebc0d 100644 --- a/tools/testtool/testdata/submissions/correct/coverme/student/coverme/coverage_test.go +++ b/tools/testtool/testdata/submissions/correct/coverme/student/coverme/coverage_test.go @@ -6,10 +6,11 @@ import ( "testing" "github.com/stretchr/testify/require" + "gitlab.com/slon/shad-go/coverme" ) -// min coverage: 60% +// min coverage: 70% func TestSum(t *testing.T) { require.Equal(t, int64(2), coverme.Sum(1, 1)) diff --git a/tools/testtool/testdata/submissions/correct/coverme/student/coverme/service.go b/tools/testtool/testdata/submissions/correct/coverme/student/coverme/service.go index d350073..48e8e89 100644 --- a/tools/testtool/testdata/submissions/correct/coverme/student/coverme/service.go +++ b/tools/testtool/testdata/submissions/correct/coverme/student/coverme/service.go @@ -2,9 +2,13 @@ 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 } diff --git a/tools/testtool/testdata/submissions/correct/coverme/student/coverme/subpkg/additional_test.go b/tools/testtool/testdata/submissions/correct/coverme/student/coverme/subpkg/additional_test.go index b624221..366a2dd 100644 --- a/tools/testtool/testdata/submissions/correct/coverme/student/coverme/subpkg/additional_test.go +++ b/tools/testtool/testdata/submissions/correct/coverme/student/coverme/subpkg/additional_test.go @@ -10,3 +10,7 @@ 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)) +} diff --git a/tools/testtool/testdata/submissions/correct/coverme/student/coverme/subpkg/g.go b/tools/testtool/testdata/submissions/correct/coverme/student/coverme/subpkg/g.go new file mode 100644 index 0000000..acbff5b --- /dev/null +++ b/tools/testtool/testdata/submissions/correct/coverme/student/coverme/subpkg/g.go @@ -0,0 +1,5 @@ +package subpkg + +func AddTwo(n int) int { + return AddOne(n + 1) +} diff --git a/tools/testtool/testdata/submissions/correct/coverme/student/go.sum b/tools/testtool/testdata/submissions/correct/coverme/student/go.sum index fb52f53..a80206a 100644 --- a/tools/testtool/testdata/submissions/correct/coverme/student/go.sum +++ b/tools/testtool/testdata/submissions/correct/coverme/student/go.sum @@ -1,6 +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= From 9ce2d571968295627f323b86f792ad44f3ffad01 Mon Sep 17 00:00:00 2001 From: Arseny Balobanov Date: Fri, 20 Mar 2020 00:57:07 +0300 Subject: [PATCH 06/11] Adding todo-app coverage task. --- coverme/README.md | 76 +++++++++++++++++++++++++++ coverme/app/app.go | 99 ++++++++++++++++++++++++++++++++++++ coverme/app/coverage_test.go | 5 ++ coverme/client/client.go | 70 +++++++++++++++++++++++++ coverme/main.go | 18 +++++++ coverme/models/storage.go | 70 +++++++++++++++++++++++++ coverme/models/todo.go | 25 +++++++++ coverme/utils/httputils.go | 32 ++++++++++++ go.mod | 2 + go.sum | 4 ++ 10 files changed, 401 insertions(+) create mode 100644 coverme/README.md create mode 100644 coverme/app/app.go create mode 100644 coverme/app/coverage_test.go create mode 100644 coverme/client/client.go create mode 100644 coverme/main.go create mode 100644 coverme/models/storage.go create mode 100644 coverme/models/todo.go create mode 100644 coverme/utils/httputils.go diff --git a/coverme/README.md b/coverme/README.md new file mode 100644 index 0000000..649407b --- /dev/null +++ b/coverme/README.md @@ -0,0 +1,76 @@ +## coverme + +В этой задаче нужно покрыть простой todo-app http сервис unit тестами. + +Имеющиеся `_test.go` файлы лучше не трогать, +при тестировании все изменения перетираются. + +Package main можно не тестировать. + +Тестирующая система будет проверяться code coverage. +Порог задан в [coverage_test.go](./app/coverage_test.go) + +Как посмотреть coverage: +``` +go test -v -cover ./coverme/... +``` + +## Ссылки + +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) для тестирования клинета + +## 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}] +``` \ No newline at end of file diff --git a/coverme/app/app.go b/coverme/app/app.go new file mode 100644 index 0000000..c28d216 --- /dev/null +++ b/coverme/app/app.go @@ -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/todo/models" + "gitlab.com/slon/shad-go/todo/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!") +} diff --git a/coverme/app/coverage_test.go b/coverme/app/coverage_test.go new file mode 100644 index 0000000..11801c2 --- /dev/null +++ b/coverme/app/coverage_test.go @@ -0,0 +1,5 @@ +// +build !change + +package app + +// min coverage: 85% diff --git a/coverme/client/client.go b/coverme/client/client.go new file mode 100644 index 0000000..07df9a4 --- /dev/null +++ b/coverme/client/client.go @@ -0,0 +1,70 @@ +// +build !change + +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + + "gitlab.com/slon/shad-go/todo/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 +} diff --git a/coverme/main.go b/coverme/main.go new file mode 100644 index 0000000..76cf45b --- /dev/null +++ b/coverme/main.go @@ -0,0 +1,18 @@ +// +build !change + +package main + +import ( + "flag" + + "gitlab.com/slon/shad-go/todo/app" + "gitlab.com/slon/shad-go/todo/models" +) + +func main() { + port := flag.Int("port", 8080, "port to listen") + flag.Parse() + + db := models.NewInMemoryStorage() + app.New(db).Start(*port) +} diff --git a/coverme/models/storage.go b/coverme/models/storage.go new file mode 100644 index 0000000..59b029f --- /dev/null +++ b/coverme/models/storage.go @@ -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 +} diff --git a/coverme/models/todo.go b/coverme/models/todo.go new file mode 100644 index 0000000..f65e8a3 --- /dev/null +++ b/coverme/models/todo.go @@ -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 +} diff --git a/coverme/utils/httputils.go b/coverme/utils/httputils.go new file mode 100644 index 0000000..d1a7ede --- /dev/null +++ b/coverme/utils/httputils.go @@ -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)) +} diff --git a/go.mod b/go.mod index c00c043..a495ba3 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,8 @@ require ( github.com/go-resty/resty/v2 v2.1.0 github.com/gofrs/uuid v3.2.0+incompatible 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/stretchr/testify v1.4.0 go.uber.org/goleak v1.0.0 diff --git a/go.sum b/go.sum index c220d13..f68d302 100644 --- a/go.sum +++ b/go.sum @@ -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/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/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/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= From 235923eba8c8b2f2bef8df47517d22926b9089c6 Mon Sep 17 00:00:00 2001 From: Arseny Balobanov Date: Fri, 20 Mar 2020 01:00:46 +0300 Subject: [PATCH 07/11] Fix imports. --- coverme/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/coverme/main.go b/coverme/main.go index 76cf45b..71478c5 100644 --- a/coverme/main.go +++ b/coverme/main.go @@ -5,8 +5,8 @@ package main import ( "flag" - "gitlab.com/slon/shad-go/todo/app" - "gitlab.com/slon/shad-go/todo/models" + "gitlab.com/slon/shad-go/coverme/app" + "gitlab.com/slon/shad-go/coverme/models" ) func main() { From fb5f9d5aadf29d6fed2bac477bc8e06c0f09689f Mon Sep 17 00:00:00 2001 From: Arseny Balobanov Date: Fri, 20 Mar 2020 01:02:50 +0300 Subject: [PATCH 08/11] Fix imports. --- coverme/app/app.go | 4 ++-- coverme/client/client.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/coverme/app/app.go b/coverme/app/app.go index c28d216..470c54b 100644 --- a/coverme/app/app.go +++ b/coverme/app/app.go @@ -13,8 +13,8 @@ import ( "github.com/gorilla/handlers" "github.com/gorilla/mux" - "gitlab.com/slon/shad-go/todo/models" - "gitlab.com/slon/shad-go/todo/utils" + "gitlab.com/slon/shad-go/coverme/models" + "gitlab.com/slon/shad-go/coverme/utils" ) type App struct { diff --git a/coverme/client/client.go b/coverme/client/client.go index 07df9a4..af4bbc9 100644 --- a/coverme/client/client.go +++ b/coverme/client/client.go @@ -8,7 +8,7 @@ import ( "fmt" "net/http" - "gitlab.com/slon/shad-go/todo/models" + "gitlab.com/slon/shad-go/coverme/models" ) type Client struct { From ad8e27c53a1a4d5f2e1b589443c9e772b7bc2fda Mon Sep 17 00:00:00 2001 From: Arseny Balobanov Date: Fri, 20 Mar 2020 01:16:07 +0300 Subject: [PATCH 09/11] coverme: increase min coverage from 85% to 90% --- coverme/app/coverage_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coverme/app/coverage_test.go b/coverme/app/coverage_test.go index 11801c2..2a9b595 100644 --- a/coverme/app/coverage_test.go +++ b/coverme/app/coverage_test.go @@ -2,4 +2,4 @@ package app -// min coverage: 85% +// min coverage: 90% From 6d948ff9da37f2cfdffad2e85db6e12aa4047268 Mon Sep 17 00:00:00 2001 From: Arseny Balobanov Date: Fri, 20 Mar 2020 01:30:21 +0300 Subject: [PATCH 10/11] Fix readme. --- coverme/README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/coverme/README.md b/coverme/README.md index 649407b..9af38b8 100644 --- a/coverme/README.md +++ b/coverme/README.md @@ -2,12 +2,13 @@ В этой задаче нужно покрыть простой todo-app http сервис unit тестами. -Имеющиеся `_test.go` файлы лучше не трогать, -при тестировании все изменения перетираются. - +Необходимо покрыть все sub-package'и. Package main можно не тестировать. -Тестирующая система будет проверяться code coverage. +Существующие файлы менять не нужно. +Нужно создавать новые файлы с тестами. + +Тестирующая система будет проверять code coverage. Порог задан в [coverage_test.go](./app/coverage_test.go) Как посмотреть coverage: @@ -15,12 +16,15 @@ Package main можно не тестировать. 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 сервисе From ccd5e77396b7ce853d15e3432fd7e2c009909ff4 Mon Sep 17 00:00:00 2001 From: Arseny Balobanov Date: Fri, 20 Mar 2020 01:38:59 +0300 Subject: [PATCH 11/11] coverme: add note about 100% coverage. --- coverme/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/coverme/README.md b/coverme/README.md index 9af38b8..c0d4376 100644 --- a/coverme/README.md +++ b/coverme/README.md @@ -11,6 +11,11 @@ Package main можно не тестировать. Тестирующая система будет проверять code coverage. Порог задан в [coverage_test.go](./app/coverage_test.go) +Важно понимать, что coverage 100% - не решение всех проблем. +В коде по-прежнему могут быть ошибки. +Coverage 100% говорит ровно о том, что все строки кода выполнялись. +Хорошие тесты в первую очередь тестируют функциональность. + Как посмотреть coverage: ``` go test -v -cover ./coverme/...