177 lines
4.1 KiB
Go
177 lines
4.1 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
|
|
"gitea.teamortix.com/team-ortix/coverage/cover"
|
|
)
|
|
|
|
const (
|
|
profilePath = "/go/coverage.out"
|
|
)
|
|
|
|
func main() {
|
|
event := os.Getenv("DRONE_BUILD_EVENT")
|
|
if event != "push" && event != "pull_request" {
|
|
log.Printf("DRONE_BUILD EVENT of '%s' skipped. Expecting 'push' or 'pull_request'", event)
|
|
return
|
|
}
|
|
|
|
namespace := os.Getenv("DRONE_REPO_NAMESPACE")
|
|
if namespace == "" {
|
|
log.Fatalln("could not find DRONE_REPO_NAMESPACE")
|
|
}
|
|
|
|
project := os.Getenv("DRONE_REPO_NAME")
|
|
if project == "" {
|
|
log.Fatalln("could not find DRONE_REPO_NAME")
|
|
}
|
|
|
|
switch event {
|
|
case "push":
|
|
branch := os.Getenv("DRONE_BRANCH")
|
|
if branch == "" {
|
|
log.Fatalln("could not find DRONE_BRANCH")
|
|
}
|
|
handlePush(namespace, project, branch)
|
|
case "pull_request":
|
|
pull := os.Getenv("DRONE_PULL_REQUEST")
|
|
if pull == "" {
|
|
log.Fatalln("could not find DRONE_PULL_REQUEST")
|
|
}
|
|
handlePullRequest(namespace, project, pull)
|
|
default:
|
|
log.Fatalf("invalid branch name: %s\n", event)
|
|
}
|
|
}
|
|
|
|
const urlFormat = "https://coverage.teamortix.com/upload/%s/%s/%s/%s"
|
|
|
|
func handlePush(namespace, project, branch string) {
|
|
tmpl, percentage := getCoverageData()
|
|
url := fmt.Sprintf(urlFormat, "branch", namespace, project, branch)
|
|
err := request(url, percentage, tmpl)
|
|
if err != nil {
|
|
log.Fatalf("could not make request: %v\n", err)
|
|
}
|
|
log.Printf("Successfully made request to %s.\n", url)
|
|
}
|
|
|
|
func handlePullRequest(namespace, project, pull string) {
|
|
tmpl, percentage := getCoverageData()
|
|
|
|
url := fmt.Sprintf(urlFormat, "pulls", namespace, project, pull)
|
|
err := request(url, percentage, tmpl)
|
|
if err != nil {
|
|
log.Fatalf("could not make request: %v\n", err)
|
|
}
|
|
log.Printf("Successfully made request to %s.\n", url)
|
|
}
|
|
|
|
// getCoverageData uses `go tool cover` to get the coverage profile.
|
|
// It expects coverage file `coverage.out` to be present in `/go`.
|
|
func getCoverageData() (cover.TemplateData, float64) {
|
|
file, err := os.Open(profilePath)
|
|
if err != nil {
|
|
log.Fatalf("could not open cover profiles: %v\n", err)
|
|
}
|
|
profiles, err := cover.ParseProfiles(bufio.NewReader(file))
|
|
if err != nil {
|
|
log.Fatalf("could not parse cover profiles: %v\n", err)
|
|
}
|
|
tmpl, err := cover.Template(profiles)
|
|
if err != nil {
|
|
log.Fatalf("could not parse cover profiles: %v\n", err)
|
|
}
|
|
coverage := percentCovered(profiles)
|
|
return tmpl, coverage
|
|
}
|
|
|
|
// percentCovered returns, as a percentage, the fraction of the statements in
|
|
// the profile covered by the test run.
|
|
// In effect, it reports the coverage of a given source file.
|
|
func percentCovered(ps []*cover.Profile) float64 {
|
|
var total, covered int64
|
|
for _, p := range ps {
|
|
for _, b := range p.Blocks {
|
|
total += int64(b.NumStmt)
|
|
if b.Count > 0 {
|
|
covered += int64(b.NumStmt)
|
|
}
|
|
}
|
|
if total == 0 {
|
|
return 0
|
|
}
|
|
}
|
|
|
|
return float64(covered) / float64(total)
|
|
}
|
|
|
|
func request(url string, coverage float64, tmpl cover.TemplateData) error {
|
|
client := &http.Client{Timeout: time.Second * 5}
|
|
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
|
|
fw, err := writer.CreateFormFile("report", "report.html")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = render(fw, tmpl)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fw, err = writer.CreateFormField("coverage")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err = fmt.Fprintf(fw, "%.03f", coverage); err != nil {
|
|
return err
|
|
}
|
|
fw, err = writer.CreateFormField("secret")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err = fw.Write([]byte(os.Getenv("COVERAGE_SECRET"))); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = writer.Close(); err != nil {
|
|
return err
|
|
}
|
|
|
|
req, err := http.NewRequestWithContext(context.Background(), "POST", url, body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
|
res, err := client.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
resBody, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf("Response from coverage server: %s", resBody)
|
|
|
|
if err = res.Body.Close(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if res.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("request did not succeed: (code %d): %s", res.StatusCode, resBody)
|
|
}
|
|
return nil
|
|
}
|