Calculate coverage of private code.

Create empty coverage profile from problem code to calculate coverage
correctly. For some wierd reasons it will also account for not tested packages.
This change prevents students from adding new code and testing it,
instead of getting good coverage for initial codebase :)
This commit is contained in:
Alexander Vasilyev 2020-03-23 23:49:51 +03:00 committed by Fedor Korotkiy
parent 54ab7b69d1
commit a56f50ac46
2 changed files with 56 additions and 20 deletions

View file

@ -83,15 +83,34 @@ func searchCoverageComment(fname string) (*CoverageRequirements, error) {
return &CoverageRequirements{}, nil return &CoverageRequirements{}, nil
} }
// calCoverage calculates coverage percent for given coverage profile. // calCoverage calculates coverage percent of code blocks recorded in targetProfile
func calCoverage(fileNames []string) (float64, error) { // by given coverage profiles.
type block struct { func calCoverage(targetProfile string, fileNames []string) (float64, error) {
type key struct {
fileName string fileName string
startLine, startCol int startLine, startCol int
endLine, endCol int endLine, endCol int
numStmt int numStmt int
} }
counts := map[block]int{} newKey := func(p *cover.Profile, b cover.ProfileBlock) key {
return key{
p.FileName,
b.StartLine, b.StartCol,
b.EndLine, b.EndCol,
b.NumStmt,
}
}
executed := map[key]bool{}
targetProfiles, err := cover.ParseProfiles(targetProfile)
if err != nil {
return 0.0, fmt.Errorf("cannot parse target profile %s: %w", targetProfile, err)
}
for _, p := range targetProfiles {
for _, b := range p.Blocks {
executed[newKey(p, b)] = false
}
}
for _, f := range fileNames { for _, f := range fileNames {
profiles, err := cover.ParseProfiles(f) profiles, err := cover.ParseProfiles(f)
@ -101,21 +120,19 @@ func calCoverage(fileNames []string) (float64, error) {
for _, p := range profiles { for _, p := range profiles {
for _, b := range p.Blocks { for _, b := range p.Blocks {
counts[block{ k := newKey(p, b)
p.FileName, if _, ok := executed[k]; ok && b.Count > 0 {
b.StartLine, b.StartCol, executed[k] = true
b.EndLine, b.EndCol, }
b.NumStmt,
}] += b.Count
} }
} }
} }
var total, covered int var total, covered int
for b, count := range counts { for k, e := range executed {
total += b.numStmt total += k.numStmt
if count > 0 { if e {
covered += b.numStmt covered += k.numStmt
} }
} }

View file

@ -243,8 +243,12 @@ func runTests(testDir, privateRepo, problem string) error {
} }
coverageReq := getCoverageRequirements(path.Join(privateRepo, problem)) coverageReq := getCoverageRequirements(path.Join(privateRepo, problem))
coveragePackages := []string{}
if coverageReq.Enabled { if coverageReq.Enabled {
log.Printf("required coverage: %.2f%%", coverageReq.Percent) log.Printf("required coverage: %.2f%%", coverageReq.Percent)
for _, pkg := range coverageReq.Packages {
coveragePackages = append(coveragePackages, path.Join(moduleImportPath, problem, pkg))
}
} }
binariesJSON, _ := json.Marshal(binaries) binariesJSON, _ := json.Marshal(binaries)
@ -254,11 +258,7 @@ func runTests(testDir, privateRepo, problem string) error {
testBinaries[testPkg] = binPath testBinaries[testPkg] = binPath
cmd := []string{"test", "-mod", "readonly", "-tags", "private", "-c", "-o", binPath, testPkg} cmd := []string{"test", "-mod", "readonly", "-tags", "private", "-c", "-o", binPath, testPkg}
if coverageReq.Enabled { if coverageReq.Enabled {
pkgs := make([]string, len(coverageReq.Packages)) cmd = append(cmd, "-cover", "-coverpkg", strings.Join(coveragePackages, ","))
for i, pkg := range coverageReq.Packages {
pkgs[i] = path.Join(moduleImportPath, problem, pkg)
}
cmd = append(cmd, "-cover", "-coverpkg", strings.Join(pkgs, ","))
} }
if err := runGo(cmd...); err != nil { 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)
@ -324,7 +324,26 @@ func runTests(testDir, privateRepo, problem string) error {
if coverageReq.Enabled { if coverageReq.Enabled {
log.Printf("checking coverage is at least %.2f%%...", coverageReq.Percent) log.Printf("checking coverage is at least %.2f%%...", coverageReq.Percent)
percent, err := calCoverage(coverProfiles) // For some reason, this command will record all coverage blocks in coverpkg,
// even if no test binaries depend on given package.
// Hacky way to record all the code present in problem definition.
targetProfile := path.Join(os.TempDir(), randomName())
coverCmd := exec.Command("go",
"test",
"-coverpkg", strings.Join(coveragePackages, ","),
"-coverprofile", targetProfile,
"-run", "^$",
"./...",
)
coverCmd.Env = append(os.Environ(), "GOFLAGS=")
coverCmd.Dir = path.Join(privateRepo, problem)
coverCmd.Stderr = os.Stderr
log.Printf("> %s", strings.Join(coverCmd.Args, " "))
if err := coverCmd.Run(); err != nil {
return fmt.Errorf("error getting target coverage profile: %w", err)
}
percent, err := calCoverage(targetProfile, coverProfiles)
if err != nil { if err != nil {
return err return err
} }