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 }