Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add unit test coverage requirements for some packages #22401

Closed
26 changes: 26 additions & 0 deletions .github/workflows/pull-db-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,20 @@ jobs:
TAGS: bindata
RACE_ENABLED: true
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }}
- uses: actions/upload-artifact@v3
with:
name: unit-test-coverage
path: coverage.out
- name: unit-tests-gogit
run: make unit-test-coverage test-check
env:
TAGS: bindata gogit
RACE_ENABLED: true
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }}
- uses: actions/upload-artifact@v3
with:
name: unit-test-coverage-gogit
path: coverage.out

test-mysql5:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
Expand Down Expand Up @@ -190,6 +198,10 @@ jobs:
RACE_ENABLED: true
USE_REPO_TEST_DIR: 1
TEST_INDEXER_CODE_ES_URL: "http://elastic:changeme@elasticsearch:9200"
- uses: actions/upload-artifact@v3
with:
name: unit-test-coverage-integration
path: integration.coverage.out

test-mysql8:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
Expand Down Expand Up @@ -251,3 +263,17 @@ jobs:
env:
TAGS: bindata
USE_REPO_TEST_DIR: 1

check-backend-coverage:
if: needs.files-changed.outputs.backend == 'true'
needs: [files-changed, test-pgsql, test-sqlite, test-unit, test-mysql5, test-mysql8, test-mssql]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: ">=1.20"
check-latest: true
- uses: actions/download-artifact@v3
- run: go run build/gocoverage.go merge unit-test-coverage/coverage.out unit-test-coverage-gogit/coverage.out unit-test-coverage-integration/coverage.out > coverage.all
Copy link
Member

@silverwind silverwind Jun 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calls like this should be in the Makefile so that coverage can be produced locally as well. Please don't lock us in into actions with commands like this.

- run: make check-backend-coverage
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ help:
@echo " - test test everything"
@echo " - test-frontend test frontend files"
@echo " - test-backend test backend files"
@echo " - check-backend-coverage check backend package coverage"
@echo " - test-e2e[\#TestSpecificName] test end to end using playwright"
@echo " - webpack build webpack files"
@echo " - svg build svg files"
Expand Down Expand Up @@ -472,13 +473,17 @@ test\#%:
coverage:
grep '^\(mode: .*\)\|\(.*:[0-9]\+\.[0-9]\+,[0-9]\+\.[0-9]\+ [0-9]\+ [0-9]\+\)$$' coverage.out > coverage-bodged.out
grep '^\(mode: .*\)\|\(.*:[0-9]\+\.[0-9]\+,[0-9]\+\.[0-9]\+ [0-9]\+ [0-9]\+\)$$' integration.coverage.out > integration.coverage-bodged.out
$(GO) run build/gocovmerge.go integration.coverage-bodged.out coverage-bodged.out > coverage.all
$(GO) run build/gocoverage.go merge integration.coverage-bodged.out coverage-bodged.out > coverage.all

.PHONY: unit-test-coverage
unit-test-coverage:
@echo "Running unit-test-coverage $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
@$(GO) test $(GOTESTFLAGS) -timeout=20m -tags='$(TEST_TAGS)' -cover -coverprofile coverage.out $(GO_TEST_PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1

.PHONY: check-backend-coverage
check-backend-coverage:
@$(GO) run build/gocoverage.go check coverage.all tests/coverage_requirement.txt

.PHONY: tidy
tidy:
$(eval MIN_GO_VERSION := $(shell grep -Eo '^go\s+[0-9]+\.[0-9.]+' go.mod | cut -d' ' -f2))
Expand Down
136 changes: 132 additions & 4 deletions build/gocovmerge.go → build/gocoverage.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@
package main

import (
"flag"
"fmt"
"io"
"log"
"math"
"os"
"path/filepath"
"sort"
"strconv"
"strings"

"golang.org/x/tools/cover"
)
Expand Down Expand Up @@ -99,12 +102,52 @@ func dumpProfiles(profiles []*cover.Profile, out io.Writer) {
}
}

func main() {
flag.Parse()
func parseArgs() (mainOptions map[string]string, subCmd string, subArgs []string) {
mainOptions = map[string]string{}
for i := 1; i < len(os.Args); i++ {
arg := os.Args[i]
if arg == "" {
break
}
if arg[0] == '-' {
arg = strings.TrimPrefix(arg, "-")
arg = strings.TrimPrefix(arg, "-")
fields := strings.SplitN(arg, "=", 2)
if len(fields) == 1 {
mainOptions[fields[0]] = "1"
} else {
mainOptions[fields[0]] = fields[1]
}
} else {
subCmd = arg
subArgs = os.Args[i+1:]
break
}
}
return
}

func showUsage() {
fmt.Printf(`Usage: %[1]s {command} [arguments]

Commands:
%[1]s merge ...
%[1]s check coverage_file requirement_file.txt

Arguments:
{file-list} the file list

Example:
%[1]s merge {file-list}
%[1]s check coverage.out requirement_file.txt

`, "gocoverage")
}

func merge(args []string) {
var merged []*cover.Profile

for _, file := range flag.Args() {
for _, file := range args {
profiles, err := cover.ParseProfiles(file)
if err != nil {
log.Fatalf("failed to parse profile '%s': %v", file, err)
Expand All @@ -116,3 +159,88 @@ func main() {

dumpProfiles(merged, os.Stdout)
}

func percentToInt64(percent string) int64 {
value := strings.ReplaceAll(percent, "%", "")
i, err := strconv.ParseFloat(value, 10)
if err != nil {
log.Fatalf("invalid percent: %s", percent)
}
return int64(i * 10)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why i*10 ? 12.34% means "123"? That's a strange unit.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some output will be 12.3%, and I think it's accurate enough.

Copy link
Contributor

@wxiaoguang wxiaoguang Jun 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say this trick is difficult to understand, and I do not understand why it must be casted to int64

IMO keep the original float "12.3" is clear enough, or unify the unit to natural unit (1.0 mean 100%, 0.02 mean 2%)

}

func profileCount(p *cover.Profile) (int64, int64) {
blocks := p.Blocks
var active, total int64
for i := range blocks {
stmts := int64(blocks[i].NumStmt)
total += stmts
if blocks[i].Count > 0 {
active += stmts
}
}
return total, active
}

func checkPackages(args []string) {
if len(args) != 2 {
log.Fatalf("invalid arguments: %v", args)
return
}
coverageFile, packagesFile := args[0], args[1]
profiles, err := cover.ParseProfiles(coverageFile)
if err != nil {
log.Fatalf("failed to parse profile '%s': %v", coverageFile, err)
}
packagesRequirements := make(map[string]int64)
packages, err := os.ReadFile(packagesFile)
if err != nil {
log.Fatalf("failed to read packages file '%s': %v", packagesFile, err)
}
lines := strings.Split(string(packages), "\n")
for _, p := range lines {
parts := strings.Split(p, "=")
if len(parts) != 2 {
continue
}
packagesRequirements[parts[0]] = percentToInt64(parts[1])
}
packagesTotals := make(map[string]int64)
packagesActives := make(map[string]int64)
for _, p := range profiles {
pkg := filepath.Dir(p.FileName)
_, ok := packagesRequirements[pkg]
if !ok {
continue
}
total, active := profileCount(p)
packagesTotals[pkg] += total
packagesActives[pkg] += active
}
var failed bool
for k, v := range packagesRequirements {
actual := 100 * float64(packagesActives[k]) / float64(packagesTotals[k])
if v > int64(math.Floor(actual*10+0.5)) {
log.Printf("package %s coverage is %.1f%%, required %.1f%%", k, actual, float64(v)/10.0)
failed = true
}
}
if failed {
os.Exit(1)
}
}

func main() {
_, subCmd, subArgs := parseArgs()
if subCmd == "" {
showUsage()
os.Exit(1)
}

switch subCmd {
case "merge":
merge(subArgs)
case "check":
checkPackages(subArgs)
}
}
126 changes: 126 additions & 0 deletions tests/coverage_requirement.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
code.gitea.io/gitea/modules/activitypub=60.66%
code.gitea.io/gitea/modules/analyze=73.91%
code.gitea.io/gitea/modules/auth=60.29%
code.gitea.io/gitea/modules/avatar=55.20%
code.gitea.io/gitea/modules/base=92.96%
code.gitea.io/gitea/modules/cache=14.5%
code.gitea.io/gitea/modules/charset=56.67%
code.gitea.io/gitea/modules/container=100.00%
code.gitea.io/gitea/modules/context=58.91%
code.gitea.io/gitea/modules/csv=100.00%
code.gitea.io/gitea/modules/doctor=15.1%
code.gitea.io/gitea/modules/emoji=83.3%
code.gitea.io/gitea/modules/eventsource=53.88%
code.gitea.io/gitea/modules/generate=0.00%
code.gitea.io/gitea/modules/git=61.93%
code.gitea.io/gitea/modules/gitgraph=75.77%
code.gitea.io/gitea/modules/graceful=29.0%
code.gitea.io/gitea/modules/hcaptcha=0.00%
code.gitea.io/gitea/modules/highlight=67.14%
code.gitea.io/gitea/modules/hostmatcher=79.34%
code.gitea.io/gitea/modules/html=100.00%
code.gitea.io/gitea/modules/httpcache=78.95%
code.gitea.io/gitea/modules/httplib=67.86%
code.gitea.io/gitea/modules/indexer=39.21%
code.gitea.io/gitea/modules/issue=75.47%
code.gitea.io/gitea/modules/json=43.75%
code.gitea.io/gitea/modules/lfs=70.98%
code.gitea.io/gitea/modules/log=69.41%
code.gitea.io/gitea/modules/markup=53.32%
code.gitea.io/gitea/modules/mcaptcha=0.00%
code.gitea.io/gitea/modules/metrics=0.00%
code.gitea.io/gitea/modules/migration=70.45%
code.gitea.io/gitea/modules/mirror=16.67%
code.gitea.io/gitea/modules/nosql=29.21%
code.gitea.io/gitea/modules/notification=64.94%
code.gitea.io/gitea/modules/options=48.74%
code.gitea.io/gitea/modules/packages=70.63%
code.gitea.io/gitea/modules/paginator=100.00%
code.gitea.io/gitea/modules/password=66.18%
code.gitea.io/gitea/modules/pprof=0.00%
code.gitea.io/gitea/modules/private=13.40%
code.gitea.io/gitea/modules/process=38.5%
code.gitea.io/gitea/modules/proxy=17.2%
code.gitea.io/gitea/modules/proxyprotocol=0.00%
code.gitea.io/gitea/modules/public=53.26%
code.gitea.io/gitea/modules/queue=39.17%
code.gitea.io/gitea/modules/recaptcha=0.00%
code.gitea.io/gitea/modules/references=80.50%
code.gitea.io/gitea/modules/regexplru=65.22%
code.gitea.io/gitea/modules/repository=49.68%
code.gitea.io/gitea/modules/secret=64.00%
code.gitea.io/gitea/modules/session=18.63%
code.gitea.io/gitea/modules/setting=54.19%
code.gitea.io/gitea/modules/sitemap=82.35%
code.gitea.io/gitea/modules/ssh=45.98%
code.gitea.io/gitea/modules/storage=57.14%
code.gitea.io/gitea/modules/structs=54.22%
code.gitea.io/gitea/modules/svg=94.4%
code.gitea.io/gitea/modules/sync=100.00%
code.gitea.io/gitea/modules/system=70.00%
code.gitea.io/gitea/modules/templates=43.01%
code.gitea.io/gitea/modules/test=68.83%
code.gitea.io/gitea/modules/timeutil=80.80%
code.gitea.io/gitea/modules/translation=60.70%
code.gitea.io/gitea/modules/typesniffer=92.86%
code.gitea.io/gitea/modules/updatechecker=0.00%
code.gitea.io/gitea/modules/upload=73.85%
code.gitea.io/gitea/modules/uri=27.78%
code.gitea.io/gitea/modules/user=23.53%
code.gitea.io/gitea/modules/util=72.93%
code.gitea.io/gitea/modules/validation=79.4%
code.gitea.io/gitea/modules/watcher=0.00%
code.gitea.io/gitea/modules/web=76.76%
code.gitea.io/gitea/modules/webhook=13.3%
code.gitea.io/gitea/models/activities=58.48%
code.gitea.io/gitea/models/admin=33.55%
code.gitea.io/gitea/models/asymkey=41.40%
code.gitea.io/gitea/models/auth=46.36%
code.gitea.io/gitea/models/avatars=31.82%
code.gitea.io/gitea/models/db=41.53%
code.gitea.io/gitea/models/git=44.19%
code.gitea.io/gitea/models/issues=57.31%
code.gitea.io/gitea/models/organization=71.00%
code.gitea.io/gitea/models/packages=71.66%
code.gitea.io/gitea/models/perm=57.05%
code.gitea.io/gitea/models/project=31.85%
code.gitea.io/gitea/models/pull=36.13%
code.gitea.io/gitea/models/repo=58.62%
code.gitea.io/gitea/models/secret=24.56%
code.gitea.io/gitea/models/system=56.16%
code.gitea.io/gitea/models/unit=48.72%
code.gitea.io/gitea/models/unittest=31.91%
code.gitea.io/gitea/models/user=55.82%
code.gitea.io/gitea/models/webhook=66.02%
code.gitea.io/gitea/routers/api=60.66%
code.gitea.io/gitea/routers/common=61.15%
code.gitea.io/gitea/routers/install=5.7%
code.gitea.io/gitea/routers/private=34.74%
code.gitea.io/gitea/routers/utils=100.00%
code.gitea.io/gitea/routers/web=28.95%
code.gitea.io/gitea/services/agit=56.59%
code.gitea.io/gitea/services/asymkey=36.73%
code.gitea.io/gitea/services/attachment=60.87%
code.gitea.io/gitea/services/auth=38.99%
code.gitea.io/gitea/services/automerge=44.83%
code.gitea.io/gitea/services/context=47.22%
code.gitea.io/gitea/services/convert=73.5%
code.gitea.io/gitea/services/cron=56.2%
code.gitea.io/gitea/services/externalaccount=0.00%
code.gitea.io/gitea/services/forms=36.5%
code.gitea.io/gitea/services/gitdiff=70.52%
code.gitea.io/gitea/services/issue=40.50%
code.gitea.io/gitea/services/lfs=57.06%
code.gitea.io/gitea/services/mailer=54.55%
code.gitea.io/gitea/services/markup=100.00%
code.gitea.io/gitea/services/migrations=35.97%
code.gitea.io/gitea/services/mirror=29.83%
code.gitea.io/gitea/services/org=49.02%
code.gitea.io/gitea/services/packages=51.94%
code.gitea.io/gitea/services/pull=43.34%
code.gitea.io/gitea/services/release=45.09%
code.gitea.io/gitea/services/repository=41.19%
code.gitea.io/gitea/services/task=43.75%
code.gitea.io/gitea/services/user=46.24%
code.gitea.io/gitea/services/webhook=66.63%
code.gitea.io/gitea/services/wiki=56.41%