github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/fstest/test_all/report.go (about)

     1  // +build go1.11
     2  
     3  package main
     4  
     5  import (
     6  	"fmt"
     7  	"html/template"
     8  	"io/ioutil"
     9  	"log"
    10  	"os"
    11  	"os/exec"
    12  	"path"
    13  	"sort"
    14  	"time"
    15  
    16  	"github.com/ncw/rclone/fs"
    17  	"github.com/skratchdot/open-golang/open"
    18  )
    19  
    20  const timeFormat = "2006-01-02-150405"
    21  
    22  // Report holds the info to make a report on a series of test runs
    23  type Report struct {
    24  	LogDir    string        // output directory for logs and report
    25  	StartTime time.Time     // time started
    26  	DateTime  string        // directory name for output
    27  	Duration  time.Duration // time the run took
    28  	Failed    Runs          // failed runs
    29  	Passed    Runs          // passed runs
    30  	Runs      []ReportRun   // runs to report
    31  	Version   string        // rclone version
    32  	Previous  string        // previous test name if known
    33  	IndexHTML string        // path to the index.html file
    34  	URL       string        // online version
    35  }
    36  
    37  // ReportRun is used in the templates to report on a test run
    38  type ReportRun struct {
    39  	Name string
    40  	Runs Runs
    41  }
    42  
    43  // NewReport initialises and returns a Report
    44  func NewReport() *Report {
    45  	r := &Report{
    46  		StartTime: time.Now(),
    47  		Version:   fs.Version,
    48  	}
    49  	r.DateTime = r.StartTime.Format(timeFormat)
    50  
    51  	// Find previous log directory if possible
    52  	names, err := ioutil.ReadDir(*outputDir)
    53  	if err == nil && len(names) > 0 {
    54  		r.Previous = names[len(names)-1].Name()
    55  	}
    56  
    57  	// Create output directory for logs and report
    58  	r.LogDir = path.Join(*outputDir, r.DateTime)
    59  	err = os.MkdirAll(r.LogDir, 0777)
    60  	if err != nil {
    61  		log.Fatalf("Failed to make log directory: %v", err)
    62  	}
    63  
    64  	// Online version
    65  	r.URL = *urlBase + r.DateTime + "/index.html"
    66  
    67  	return r
    68  }
    69  
    70  // End should be called when the tests are complete
    71  func (r *Report) End() {
    72  	r.Duration = time.Since(r.StartTime)
    73  	sort.Sort(r.Failed)
    74  	sort.Sort(r.Passed)
    75  	r.Runs = []ReportRun{
    76  		{Name: "Failed", Runs: r.Failed},
    77  		{Name: "Passed", Runs: r.Passed},
    78  	}
    79  }
    80  
    81  // AllPassed returns true if there were no failed tests
    82  func (r *Report) AllPassed() bool {
    83  	return len(r.Failed) == 0
    84  }
    85  
    86  // RecordResult should be called with a Run when it has finished to be
    87  // recorded into the Report
    88  func (r *Report) RecordResult(t *Run) {
    89  	if !t.passed() {
    90  		r.Failed = append(r.Failed, t)
    91  	} else {
    92  		r.Passed = append(r.Passed, t)
    93  	}
    94  }
    95  
    96  // Title returns a human readable summary title for the Report
    97  func (r *Report) Title() string {
    98  	if r.AllPassed() {
    99  		return fmt.Sprintf("PASS: All tests finished OK in %v", r.Duration)
   100  	}
   101  	return fmt.Sprintf("FAIL: %d tests failed in %v", len(r.Failed), r.Duration)
   102  }
   103  
   104  // LogSummary writes the summary to the log file
   105  func (r *Report) LogSummary() {
   106  	log.Printf("Logs in %q", r.LogDir)
   107  
   108  	// Summarise results
   109  	log.Printf("SUMMARY")
   110  	log.Println(r.Title())
   111  	if !r.AllPassed() {
   112  		for _, t := range r.Failed {
   113  			log.Printf("  * %s", toShell(t.nextCmdLine()))
   114  			log.Printf("    * Failed tests: %v", t.failedTests)
   115  		}
   116  	}
   117  }
   118  
   119  // LogHTML writes the summary to index.html in LogDir
   120  func (r *Report) LogHTML() {
   121  	r.IndexHTML = path.Join(r.LogDir, "index.html")
   122  	out, err := os.Create(r.IndexHTML)
   123  	if err != nil {
   124  		log.Fatalf("Failed to open index.html: %v", err)
   125  	}
   126  	defer func() {
   127  		err := out.Close()
   128  		if err != nil {
   129  			log.Fatalf("Failed to close index.html: %v", err)
   130  		}
   131  	}()
   132  	err = reportTemplate.Execute(out, r)
   133  	if err != nil {
   134  		log.Fatalf("Failed to execute template: %v", err)
   135  	}
   136  	_ = open.Start("file://" + r.IndexHTML)
   137  }
   138  
   139  var reportHTML = `<!DOCTYPE html>
   140  <html lang="en">
   141  <head>
   142  <meta charset="utf-8">
   143  <title>{{ .Title }}</title>
   144  <style>
   145  table {
   146  	border-collapse: collapse;
   147  	border-spacing: 0;
   148  	border: 1px solid #ddd;
   149  }
   150  table.tests {
   151  	width: 100%;
   152  }
   153  table, th, td {
   154  	border: 1px solid #264653;
   155  }
   156  .Failed {
   157  	color: #BE5B43;
   158  }
   159  .Passed {
   160  	color: #17564E;
   161  }
   162  .false {
   163  	font-weight: lighter;
   164  }
   165  .true {
   166  	font-weight: bold;
   167  }
   168  
   169  th, td {
   170  	text-align: left;
   171  	padding: 4px;
   172  }
   173  
   174  tr:nth-child(even) {
   175      background-color: #f2f2f2;
   176  }
   177  
   178  a {
   179  	color: #5B1955;
   180  	text-decoration: none;
   181  }
   182  a:hover, a:focus {
   183  	color: #F4A261;
   184  	text-decoration:underline;
   185  }
   186  a:focus {
   187  	outline: thin dotted;
   188  	outline: 5px auto;
   189  }
   190  </style>
   191  </head>
   192  <body>
   193  <h1>{{ .Title }}</h1>
   194  
   195  <table>
   196  <tr><th>Version</th><td>{{ .Version }}</td></tr>
   197  <tr><th>Test</th><td><a href="{{ .URL }}">{{ .DateTime}}</a></td></tr>
   198  <tr><th>Duration</th><td>{{ .Duration }}</td></tr>
   199  {{ if .Previous}}<tr><th>Previous</th><td><a href="../{{ .Previous }}/index.html">{{ .Previous }}</a></td></tr>{{ end }}
   200  <tr><th>Up</th><td><a href="../">Older Tests</a></td></tr>
   201  </table>
   202  
   203  {{ range .Runs }}
   204  {{ if .Runs }}
   205  <h2 class="{{ .Name }}">{{ .Name }}: {{ len .Runs }}</h2>
   206  <table class="{{ .Name }} tests">
   207  <tr>
   208  <th>Backend</th>
   209  <th>Remote</th>
   210  <th>Test</th>
   211  <th>SubDir</th>
   212  <th>FastList</th>
   213  <th>Failed</th>
   214  <th>Logs</th>
   215  </tr>
   216  {{ $prevBackend := "" }}
   217  {{ $prevRemote := "" }}
   218  {{ range .Runs}}
   219  <tr>
   220  <td>{{ if ne $prevBackend .Backend }}{{ .Backend }}{{ end }}{{ $prevBackend = .Backend }}</td>
   221  <td>{{ if ne $prevRemote .Remote }}{{ .Remote }}{{ end }}{{ $prevRemote = .Remote }}</td>
   222  <td>{{ .Path }}</td>
   223  <td><span class="{{ .SubDir }}">{{ .SubDir }}</span></td>
   224  <td><span class="{{ .FastList }}">{{ .FastList }}</span></td>
   225  <td>{{ .FailedTests }}</td>
   226  <td>{{ range $i, $v := .Logs }}<a href="{{ $v }}">#{{ $i }}</a> {{ end }}</td>
   227  </tr>
   228  {{ end }}
   229  </table>
   230  {{ end }}
   231  {{ end }}
   232  </body>
   233  </html>
   234  `
   235  
   236  var reportTemplate = template.Must(template.New("Report").Parse(reportHTML))
   237  
   238  // EmailHTML sends the summary report to the email address supplied
   239  func (r *Report) EmailHTML() {
   240  	if *emailReport == "" || r.IndexHTML == "" {
   241  		return
   242  	}
   243  	log.Printf("Sending email summary to %q", *emailReport)
   244  	cmdLine := []string{"mail", "-a", "Content-Type: text/html", *emailReport, "-s", "rclone integration tests: " + r.Title()}
   245  	cmd := exec.Command(cmdLine[0], cmdLine[1:]...)
   246  	in, err := os.Open(r.IndexHTML)
   247  	if err != nil {
   248  		log.Fatalf("Failed to open index.html: %v", err)
   249  	}
   250  	cmd.Stdin = in
   251  	cmd.Stdout = os.Stdout
   252  	cmd.Stderr = os.Stderr
   253  	err = cmd.Run()
   254  	if err != nil {
   255  		log.Fatalf("Failed to send email: %v", err)
   256  	}
   257  	_ = in.Close()
   258  }
   259  
   260  // uploadTo uploads a copy of the report online to the dir given
   261  func (r *Report) uploadTo(uploadDir string) {
   262  	dst := path.Join(*uploadPath, uploadDir)
   263  	log.Printf("Uploading results to %q", dst)
   264  	cmdLine := []string{"rclone", "sync", "--stats-log-level", "NOTICE", r.LogDir, dst}
   265  	cmd := exec.Command(cmdLine[0], cmdLine[1:]...)
   266  	cmd.Stdout = os.Stdout
   267  	cmd.Stderr = os.Stderr
   268  	err := cmd.Run()
   269  	if err != nil {
   270  		log.Fatalf("Failed to upload results: %v", err)
   271  	}
   272  }
   273  
   274  // Upload uploads a copy of the report online
   275  func (r *Report) Upload() {
   276  	if *uploadPath == "" || r.IndexHTML == "" {
   277  		return
   278  	}
   279  	// Upload into dated directory
   280  	r.uploadTo(r.DateTime)
   281  	// And again into current
   282  	r.uploadTo("current")
   283  }