diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..8a425b7 Binary files /dev/null and b/.DS_Store differ diff --git a/README.rst b/README.rst index b159c1c..91bd1b8 100644 --- a/README.rst +++ b/README.rst @@ -1,16 +1,14 @@ Gocov HTML export ================= -This is a simple helper tool for generating HTML output from `axw/gocov`_. +This is a simple helper tool for generating HTML output from `axw/gocov`_ -.. _axw/gocov: https://github.com/axw/gocov +.. _axw/gocov: https://github.com/axw/gocov -Here is a screenshot: +and it forked from `matm/gocov-html`_. + +.. _matm/gocov-html: https://github.com/matm/gocov-html -.. image:: https://github.com/matm/gocov-html/blob/master/gocovh-html.png - :scale: 40 % - :alt: HTML coverage report screenshot - :align: center Installation @@ -19,20 +17,39 @@ Installation Just type the following to install the program and its dependencies:: $ go get github.com/axw/gocov/gocov - $ go get -u gopkg.in/matm/v1/gocov-html + $ go get github.com/XingyanLee/gocov-html Usage ----- + +first step: Set environment variable `GO_COV_HTML_REORDER`:: + + $ export GO_COV_HTML_REORDER=1 + `gocov-html` can read a JSON file or read from standard input:: - $ gocov test net/http | gocov-html > http.html + $ gocov convert c.out | gocov-html > http.html or:: $ gocov test net/http > http.json $ gocov-html http.json > http.html +if you want to use former sorting algorithm,just set `GO_COV_HTML_REORDER` to null or do not set environment variable `GO_COV_HTML_REORDER`. + + +if you want to compare current and previous code coverage file, the former must be a json file and need be written after the filed '-diff' . In addition,you need set an environment variable `GO_COV_HTML_REORDER_TOPN` whose value is up to you. Its default value is 10. These two command will show `GO_COV_HTML_REORDER_TOPN` functions whose coverage is less than the former file:: + + $ export GO_COV_HTML_REORDER_TOPN=10 + $ gocov convert c.out | gocov-html -diff formerfilename.json> http.html + + +if you want to see functions whose coverage is less than a certain value in current code coverage file, you just need to set a environment variable `GO_COV_HTML_REORDER_NEWFUNLIMIT` whose value is up to you. this command will highlight the functions whose coverage is less than `GO_COV_HTML_REORDER_NEWFUNLIMIT`:: + + $ export GO_COV_HTML_REORDER_NEWFUNLIMIT=90 + + The generated HTML content comes along with a default embedded CSS. Use the `-s` flag to use a custom stylesheet:: diff --git a/cov/const.go b/cov/const.go index 3944efe..bbd8234 100644 --- a/cov/const.go +++ b/cov/const.go @@ -101,7 +101,7 @@ const ( text-align: center; font-size: 20px; font-weight: bold; - color: 375eab; + color: #375eab; } .funcname { text-align: left; @@ -149,7 +149,7 @@ const ( padding: 10px; margin: 20px; line-height: 18px; - font-size; 14px; + font-size: 14px; } a { text-decoration: none; diff --git a/cov/report.go b/cov/report.go index 2c51030..25bb1ab 100644 --- a/cov/report.go +++ b/cov/report.go @@ -26,9 +26,11 @@ import ( "github.com/axw/gocov" "io" "io/ioutil" + "log" "os" "path/filepath" "sort" + "strconv" "text/tabwriter" "time" ) @@ -49,7 +51,9 @@ type report struct { type reportFunction struct { *gocov.Function - statementsReached int + statementsReached int + diff float64 + newFunctionFlag bool } type reportFunctionList []reportFunction @@ -58,19 +62,61 @@ func (l reportFunctionList) Len() int { return len(l) } -// TODO make sort method configurable? -func (l reportFunctionList) Less(i, j int) bool { - var left, right float64 - if len(l[i].Statements) > 0 { - left = float64(l[i].statementsReached) / float64(len(l[i].Statements)) +var IsMissingLine bool +var IsCompareOldFile bool +var topN = 10 +var newFunctionLimit int +func init() { + + if os.Getenv("GO_COV_HTML_REORDER") != "" { + IsMissingLine = true } - if len(l[j].Statements) > 0 { - right = float64(l[j].statementsReached) / float64(len(l[j].Statements)) + if os.Getenv("GO_COV_HTML_REORDER_TOPN") != ""{ + str := os.Getenv("GO_COV_HTML_REORDER_TOPN") + topN = validInput(str) + } - if left < right { - return true + if os.Getenv("GO_COV_HTML_REORDER_NEWFUNLIMIT") != ""{ + str := os.Getenv("GO_COV_HTML_REORDER_NEWFUNLIMIT") + newFunctionLimit = validInput(str) + } +} + +// valid In +func validInput(str string) (int) { + value, err := strconv.Atoi(str) + if err != nil{ + log.Fatal("environment variable setting error") + } + return value +} + +func (l reportFunctionList) Less(i, j int) bool { + if IsMissingLine { + var left, right int + if len(l[i].Statements) > 0 { + left = int(l[i].statementsReached) - int(len(l[i].Statements)) + } + if len(l[j].Statements) > 0 { + right = int(l[j].statementsReached) - int(len(l[j].Statements)) + } + if left > right { + return true + } + return left == right && len(l[i].Statements) > len(l[j].Statements) + } else { + var left, right float64 + if len(l[i].Statements) > 0 { + left = float64(l[i].statementsReached) / float64(len(l[i].Statements)) + } + if len(l[j].Statements) > 0 { + right = float64(l[j].statementsReached) / float64(len(l[j].Statements)) + } + if left < right { + return true + } + return left == right && len(l[i].Statements) < len(l[j].Statements) } - return left == right && len(l[i].Statements) < len(l[j].Statements) } func (l reportFunctionList) Swap(i, j int) { @@ -111,7 +157,8 @@ func (r *report) clear() { } type reportPackageList []reportPackage - +type Foo interface { +} type reportPackage struct { pkg *gocov.Package functions reportFunctionList @@ -127,28 +174,116 @@ func (rp *reportPackage) percentageReached() float64 { return rv } -func buildReportPackage(pkg *gocov.Package) reportPackage { +// search package in old file +func boolOldPackages(r *report, p *gocov.Package) (int, error) { + i := sort.Search(len(r.packages), func(i int) bool { + return r.packages[i].Name == p.Name + }) + if i < len(r.packages) && r.packages[i].Name == p.Name { + return i, nil + } + return 0, fmt.Errorf("package Name not in old file: %q ", p.Name) +} + +// search function in package after it's package has found in old file +func addOldFunction(p *gocov.Package, function *gocov.Function) (float64,bool,error) { + for j, fn := range p.Functions { + if fn.Name == function.Name { + if len(p.Functions[j].Statements) != 0 { + reached := 0 + for _, stmt := range p.Functions[j].Statements { + if stmt.Reached > 0 { + reached++ + } + } + oldReach := float64(reached) / float64(len(p.Functions[j].Statements)) * 100 + return oldReach,false, nil + } + } + } + return 0,true, fmt.Errorf("Function Name not in old file: %q ", function.Name) +} + +type diffSort struct { + name string + diff float64 +} +// use slice,top N +func diffSortFunctions(reportPackages reportPackageList) []diffSort{ + var ds []diffSort + for _, rp := range reportPackages { + for _ ,fn := range rp.functions{ + name := rp.pkg.Name + "/"+fn.Name + df := diffSort{ + name : name, + diff : fn.diff, + } + i := sort.Search(len(ds), func(i int) bool { + return ds[i].diff <= df.diff + }) + head := ds[:i] + tail := append([]diffSort{df}, ds[i:]...) + ds = append(head,tail...) + } + } + if topN > len(ds){ + topN = len(ds) + } + ds = ds[:topN] + return ds +} + + +func buildReportPackage(pkg *gocov.Package, oldr *report) reportPackage { rv := reportPackage{ pkg: pkg, functions: make(reportFunctionList, len(pkg.Functions)), } - for i, fn := range pkg.Functions { - reached := 0 - for _, stmt := range fn.Statements { - if stmt.Reached > 0 { - reached++ + if IsCompareOldFile { + olditem, err := boolOldPackages(oldr, pkg) + var p *gocov.Package + if err == nil { + p = oldr.packages[olditem] + } + for i, fn := range pkg.Functions { + reached := 0 + for _, stmt := range fn.Statements { + if stmt.Reached > 0 { + reached++ + } + } + var oldReached float64 + var newFunctionFlag bool + if err == nil { + oldReached,newFunctionFlag,_ = addOldFunction(p, fn) + } + diff := oldReached - float64(reached) / float64(len(fn.Statements)) * 100 + if diff <= 0{ + diff = 0 } + rv.functions[i] = reportFunction{fn, reached, diff,newFunctionFlag } + rv.totalStatements += len(fn.Statements) + rv.reachedStatements += reached + } + } else { + for i, fn := range pkg.Functions { + reached := 0 + for _, stmt := range fn.Statements { + if stmt.Reached > 0 { + reached++ + } + } + rv.functions[i] = reportFunction{fn, reached, 0,true} + rv.totalStatements += len(fn.Statements) + rv.reachedStatements += reached } - rv.functions[i] = reportFunction{fn, reached} - rv.totalStatements += len(fn.Statements) - rv.reachedStatements += reached } sort.Sort(reverse{rv.functions}) return rv } // PrintReport prints a coverage report to the given writer. -func printReport(w io.Writer, r *report) { +func printReport(w io.Writer, r *report, r2 *report) { css := defaultCSS if len(r.stylesheet) > 0 { css = fmt.Sprintf("", r.stylesheet) @@ -157,7 +292,7 @@ func printReport(w io.Writer, r *report) { reportPackages := make(reportPackageList, len(r.packages)) for i, pkg := range r.packages { - reportPackages[i] = buildReportPackage(pkg) + reportPackages[i] = buildReportPackage(pkg, r2) } if len(reportPackages) == 0 { @@ -172,10 +307,11 @@ func printReport(w io.Writer, r *report) { if len(reportPackages) > 1 { summaryPackage = printReportOverview(w, reportPackages) } - w = tabwriter.NewWriter(w, 0, 8, 0, '\t', 0) + + ds := diffSortFunctions(reportPackages) for _, rp := range reportPackages { - printPackage(w, r, rp) + printPackage(w, r, rp , ds) fmt.Fprintln(w) } @@ -212,24 +348,54 @@ func printReportOverview(w io.Writer, reportPackages reportPackageList) reportPa return rv } -func printPackage(w io.Writer, r *report, rp reportPackage) { +func printPackage(w io.Writer, r *report, rp reportPackage,ds []diffSort) { fmt.Fprintf(w, "
Package Overview: %s %.2f%%
", rp.pkg.Name, rp.pkg.Name, rp.percentageReached()) fmt.Fprintf(w, overview, rp.pkg.Name, rp.pkg.Name) fmt.Fprintf(w, "\n") for _, fn := range rp.functions { + // missNumber + missingNumber := len(fn.Statements) - fn.statementsReached + reached := fn.statementsReached var stmtPercent float64 = 0 if len(fn.Statements) > 0 { stmtPercent = float64(reached) / float64(len(fn.Statements)) * 100 } - fmt.Fprintf(w, "\n", + //log.Print(fn.Name,fn.newFunctionFlag,stmtPercent) + + if newFunctionLimit!=0 && fn.newFunctionFlag && stmtPercent", fn.Name, fn.Name, fn.Name, rp.pkg.Name, filepath.Base(fn.File), stmtPercent, reached, len(fn.Statements)) + if IsMissingLine { + fmt.Fprintf(w, "", missingNumber) + } + if IsCompareOldFile { + name := rp.pkg.Name + "/"+fn.Name + if fn.diff > 0 { + for _ , df := range(ds){ + if name == df.name{ + fmt.Fprintf(w, "", fn.diff) + break + } + } + } + } + fmt.Fprintln(w, "\n") } + allMissingNumber := rp.totalStatements - rp.reachedStatements - fmt.Fprintf(w, "\n", + fmt.Fprintf(w, "", rp.pkg.Name, rp.percentageReached(), rp.reachedStatements, rp.totalStatements) + if IsMissingLine { + fmt.Fprintf(w, "", allMissingNumber) + } + fmt.Fprintln(w, "\n") fmt.Fprintf(w, "
%s(...)%s/%s%.2f%%%d/%d
%s(...)%s/%s%.2f%%%d/%d%d↓ %.2f%%
%s%.2f%%%d/%d
%s%.2f%%%d/%d%d
\n") // Embbed function source code @@ -252,10 +418,26 @@ func exists(path string) (bool, error) { // parsing JSON data generated by axw/gocov. The css parameter // is an absolute path to a custom stylesheet. Use an empty // string to use the default stylesheet available. -func HTMLReportCoverage(r io.Reader, css string) error { +func HTMLReportCoverage(r io.Reader, css string, oldFileName string,) error { report := newReport() - - // Custom stylesheet? + oldreport:= newReport() + if oldFileName != "" { + IsCompareOldFile = true + var oldR io.Reader + var errOldfile error + if oldR, errOldfile = os.Open(oldFileName); errOldfile != nil { + return fmt.Errorf("Usage: %s data.json\n", oldFileName) + } + err := oldreport.readFile(oldR) + if err != nil { + return err + } + } + err := report.readFile(r) + if err != nil { + return err + } + // Custom stylesheet stylesheet := "" if len(css) > 0 { if _, err := exists(css); err != nil { @@ -264,21 +446,21 @@ func HTMLReportCoverage(r io.Reader, css string) error { stylesheet = css } report.stylesheet = stylesheet + printReport(os.Stdout, report, oldreport) + return nil +} +func (re *report) readFile(r io.Reader) (error) { data, err := ioutil.ReadAll(r) if err != nil { - return fmt.Errorf("failed to read coverage data: %s\n", err) + return fmt.Errorf("failed to unmarshal coverage data: %s\n", err) } - packages, err := unmarshalJson(data) if err != nil { return fmt.Errorf("failed to unmarshal coverage data: %s\n", err) } - for _, pkg := range packages { - report.addPackage(pkg) + re.addPackage(pkg) } - fmt.Println() - printReport(os.Stdout, report) return nil } diff --git a/gocov-html.go b/gocov-html.go index f785f6e..3039598 100644 --- a/gocov-html.go +++ b/gocov-html.go @@ -22,7 +22,7 @@ package main import ( "flag" - "github.com/matm/gocov-html/cov" + "github.com/XingyanLee/gocov-html/cov" "io" "log" "os" @@ -33,21 +33,24 @@ func main() { log.SetFlags(0) var s = flag.String("s", "", "path to custom CSS file") + var oldFileName = flag.String("diff", "", "path to old json file") flag.Parse() - + //var oldFileName string switch flag.NArg() { case 0: r = os.Stdin case 1: var err error + //log.Print(flag.Arg(0)) if r, err = os.Open(flag.Arg(0)); err != nil { log.Fatal(err) } default: log.Fatalf("Usage: %s data.json\n", os.Args[0]) } - - if err := cov.HTMLReportCoverage(r, *s); err != nil { + //log.Print("oldFileName1:",*oldFileName) + if err := cov.HTMLReportCoverage(r, *s, *oldFileName); err != nil { log.Fatal(err) } } +