github.com/covergates/covergates@v0.2.2-0.20201009050117-42ef8a19fb95/service/perl/report.go (about)

     1  package perl
     2  
     3  import (
     4  	"archive/zip"
     5  	"bytes"
     6  	"context"
     7  	"encoding/json"
     8  	"errors"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"os"
    13  	"path/filepath"
    14  	"strings"
    15  
    16  	"github.com/covergates/covergates/core"
    17  	"github.com/covergates/covergates/modules/archive"
    18  	"github.com/covergates/covergates/modules/util"
    19  )
    20  
    21  var errCoverDatabaseNotFound = errors.New("Coverage database not found")
    22  var errDigestNoFound = errors.New("Digest not found")
    23  
    24  type errDigestFormat struct {
    25  	msg string
    26  }
    27  
    28  func (err *errDigestFormat) Error() string {
    29  	return fmt.Sprintf("digest format error: %s", err.msg)
    30  }
    31  
    32  const coverDBName = "cover.14"
    33  const coverFolderName = "cover_db"
    34  const digiestFolder = "structure"
    35  
    36  type digestsMap map[string]*coverDigest
    37  
    38  // CoverageService for Perl
    39  type CoverageService struct{}
    40  
    41  // Report coverage for Perl
    42  func (r *CoverageService) Report(
    43  	ctx context.Context,
    44  	data io.Reader,
    45  ) (*core.CoverageReport, error) {
    46  
    47  	z, err := archive.NewZipReader(data)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  	db, err := findCoverDB(z)
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  	digests, err := findDigests(z.File)
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  	return report(db, digests)
    60  }
    61  
    62  // Find coverage report of Perl from given path
    63  func (r *CoverageService) Find(ctx context.Context, path string) (string, error) {
    64  	if !util.IsDir(path) {
    65  		return "", fmt.Errorf("not found")
    66  	}
    67  	if filepath.Base(path) == coverFolderName {
    68  		return path, nil
    69  	}
    70  	report := ""
    71  	err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
    72  		if err != nil {
    73  			return err
    74  		}
    75  		if info.IsDir() && filepath.Base(path) == coverFolderName {
    76  			report = path
    77  			return io.EOF
    78  		}
    79  		return nil
    80  	})
    81  	if err != nil && err != io.EOF {
    82  		return "", err
    83  	}
    84  	if report == "" {
    85  		return report, fmt.Errorf("perl coverage report not found")
    86  	}
    87  	return report, nil
    88  }
    89  
    90  // Open coverage report
    91  func (r *CoverageService) Open(ctx context.Context, path string) (io.Reader, error) {
    92  	if !util.IsDir(path) {
    93  		return nil, fmt.Errorf("%s is not a folder", path)
    94  	}
    95  	root := strings.TrimRight(path, "/")
    96  	buf := new(bytes.Buffer)
    97  	w := zip.NewWriter(buf)
    98  	defer w.Close()
    99  	err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
   100  		if len(path) <= len(root) {
   101  			return nil
   102  		}
   103  		name := path[len(root)+1:]
   104  		if info.IsDir() {
   105  			if _, err := w.Create(name + "/"); err != nil {
   106  				return err
   107  			}
   108  		} else {
   109  			f, err := w.Create(name)
   110  			if err != nil {
   111  				return err
   112  			}
   113  			data, err := ioutil.ReadFile(path)
   114  			if err != nil {
   115  				return err
   116  			}
   117  			if _, err := f.Write(data); err != nil {
   118  				return err
   119  			}
   120  		}
   121  		return nil
   122  	})
   123  	return buf, err
   124  }
   125  
   126  func findDigests(files []*zip.File) (digestsMap, error) {
   127  	digests := make(digestsMap)
   128  	for _, file := range files {
   129  		if file.FileInfo().IsDir() {
   130  			continue
   131  		}
   132  		if filepath.Dir(file.Name) == digiestFolder {
   133  			name := filepath.Base(file.Name)
   134  			if filepath.Ext(name) == ".lock" {
   135  				continue
   136  			}
   137  			r, err := file.Open()
   138  			defer r.Close()
   139  			digest, err := unmarshalCoverDigest(r)
   140  			if err != nil {
   141  				return nil, err
   142  			}
   143  			digests[name] = digest
   144  		}
   145  	}
   146  	return digests, nil
   147  }
   148  
   149  func unmarshalCoverDigest(r io.Reader) (*coverDigest, error) {
   150  	var m map[string]interface{}
   151  	data, err := ioutil.ReadAll(r)
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  	if err := json.Unmarshal(data, &m); err != nil {
   156  		return nil, err
   157  	}
   158  	fileData, ok := m["file"]
   159  	if !ok {
   160  		return nil, &errDigestFormat{msg: "file not found"}
   161  	}
   162  	file, ok := fileData.(string)
   163  	if !ok {
   164  		return nil, &errDigestFormat{msg: "file is not string"}
   165  	}
   166  
   167  	statementData, ok := m["statement"]
   168  	if !ok {
   169  		return nil, &errDigestFormat{msg: "statement not found"}
   170  	}
   171  	statementSlice, ok := statementData.([]interface{})
   172  	if !ok {
   173  		return nil, &errDigestFormat{msg: "statement is not array"}
   174  	}
   175  
   176  	statements, err := util.ToIntSlice(statementSlice)
   177  	if err != nil {
   178  		return nil, err
   179  	}
   180  	return &coverDigest{
   181  		File:      file,
   182  		Statement: statements,
   183  	}, nil
   184  }
   185  
   186  func findCoverDB(z *zip.Reader) (*coverDB, error) {
   187  	files := make(map[string]*zip.File)
   188  	for _, file := range z.File {
   189  		files[file.Name] = file
   190  	}
   191  	cover, ok := files[coverDBName]
   192  	if !ok {
   193  		return nil, errCoverDatabaseNotFound
   194  	}
   195  	content, err := cover.Open()
   196  	defer content.Close()
   197  	if err != nil {
   198  		return nil, err
   199  	}
   200  	d, err := ioutil.ReadAll(content)
   201  	if err != nil {
   202  		return nil, err
   203  	}
   204  	db := &coverDB{}
   205  	if err := json.Unmarshal(d, db); err != nil {
   206  		return nil, err
   207  	}
   208  	return db, nil
   209  }
   210  
   211  func avgStatementCoverage(files []*core.File) float64 {
   212  	if len(files) <= 0 {
   213  		return 0.0
   214  	}
   215  	s := float64(0)
   216  	for _, file := range files {
   217  		s += file.StatementCoverage
   218  	}
   219  	return s / float64(len(files))
   220  }
   221  
   222  func report(db *coverDB, digests digestsMap) (*core.CoverageReport, error) {
   223  	fileCollection := newFileCollection()
   224  	for _, run := range db.Runs {
   225  		for name, count := range run.Counts {
   226  			key, ok := run.Digests[name]
   227  			if !ok {
   228  				return nil, errDigestNoFound
   229  			}
   230  			digest, ok := digests[key]
   231  			if !ok {
   232  				return nil, errDigestNoFound
   233  			}
   234  			fileCollection.add(newFile(name, count, digest))
   235  		}
   236  	}
   237  	files := fileCollection.mergedFiles()
   238  	report := &core.CoverageReport{
   239  		StatementCoverage: avgStatementCoverage(files),
   240  		Files:             files,
   241  	}
   242  	return report, nil
   243  }