diff --git a/coverage/reader.go b/coverage/reader.go new file mode 100644 index 0000000..2286676 --- /dev/null +++ b/coverage/reader.go @@ -0,0 +1,116 @@ +package coverage + +import ( + "bufio" + "fmt" + "io" + "strconv" + "strings" +) + +// Parse creates a coverage files struct from a coverage file format +// you must provide @gopkg which is the package name +func Parse(r io.Reader, gopkg string) (*Files, error) { + reader := bufio.NewReader(r) + eof := false + var n uint = 0 + + files := &Files{ + files: make(map[string]File), + packagePath: gopkg, + } + + // read loop + for true { + n++ + if eof { + break + } + + notrim, err := reader.ReadString('\n') + if err == io.EOF { + if len(notrim) > 0 { + eof = true + } else { + break + } + } + + line := strings.Trim(notrim, " \t\r\n") + if len(line) < 1 { + continue + } + + // ignore first line + if n == 1 && line == "mode: set" { + continue + } + + // extract features from line + var cover = Coverage{} + + // format: filename:startLine.startColumn,endLine.endColumn occurences count + splitFile := strings.Split(line, ":") + if len(splitFile) != 2 { + return nil, fmt.Errorf("invalid format line %d", n) + } + filename := splitFile[0] + if !strings.HasPrefix(filename, gopkg) { + return nil, fmt.Errorf("invalid package name '%s' on line %d", gopkg, n) + } + filename = strings.TrimPrefix(filename, gopkg) + if len(filename) < 2 { + return nil, fmt.Errorf("invalid format line %d", n) + } + // remove starting '/' + if filename[0] == '/' { + filename = filename[1:] + } + + splitSpace := strings.Split(splitFile[1], " ") + if len(splitSpace) != 3 { + return nil, fmt.Errorf("invalid format line %d", n) + } + + separators := strings.Split(splitSpace[0], ",") + if len(separators) != 2 { + return nil, fmt.Errorf("invalid format line %d", n) + } + + splitStart := strings.Split(separators[0], ".") + if len(splitStart) != 2 { + return nil, fmt.Errorf("invalid format line %d", n) + } + cover.StartLine, err = strconv.ParseUint(splitStart[0], 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid format line %d", n) + } + cover.StartColumn, err = strconv.ParseUint(splitStart[1], 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid format line %d", n) + } + + splitEnd := strings.Split(separators[1], ".") + if len(splitEnd) != 2 { + return nil, fmt.Errorf("invalid format line %d", n) + } + cover.EndLine, err = strconv.ParseUint(splitEnd[0], 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid format line %d", n) + } + cover.EndColumn, err = strconv.ParseUint(splitEnd[1], 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid format line %d", n) + } + + // append to files + _, isset := files.files[filename] + if !isset { + files.files[filename] = make(File, 0) + } + files.files[filename] = append(files.files[filename], cover) + + } + + return files, nil +} diff --git a/coverage/types.go b/coverage/types.go new file mode 100644 index 0000000..a04e36b --- /dev/null +++ b/coverage/types.go @@ -0,0 +1,43 @@ +package coverage + +import "strings" + +// Files contains coverage files indexed by file path +type Files struct { + packagePath string + files map[string]File +} + +// File represents every coverage chunk for a file +type File []Coverage + +// Coverage represents a covered chunk +type Coverage struct { + StartLine uint64 + StartColumn uint64 + EndLine uint64 + EndColumn uint64 +} + +// GetFile returns the formatted coverage for the file located at @path +func (f *Files) GetFile(path string) []File { + var isDir bool = !strings.HasSuffix(path, ".go") + + files := make([]File, 0) + + // search in coverage files + for fPath, fileCoverage := range f.files { + + if !isDir && path == fPath { + files = append(files, fileCoverage) + // if not directory search, stop at first result + break + } + + if isDir && (path == "." || strings.HasPrefix(fPath, path)) { + files = append(files, fileCoverage) + } + } + + return files +}