github.com/saucelabs/saucectl@v0.175.1/internal/report/spotlight/spotlight.go (about)

     1  package spotlight
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"sync"
     7  
     8  	"github.com/fatih/color"
     9  	"github.com/saucelabs/saucectl/internal/imagerunner"
    10  	"github.com/saucelabs/saucectl/internal/job"
    11  	"github.com/saucelabs/saucectl/internal/junit"
    12  	"github.com/saucelabs/saucectl/internal/report"
    13  )
    14  
    15  // Reporter implements report.Reporter and highlights the most important test
    16  // results.
    17  type Reporter struct {
    18  	TestResults []report.TestResult
    19  	Dst         io.Writer
    20  	lock        sync.Mutex
    21  }
    22  
    23  // Add adds the test result that can be rendered by Render.
    24  func (r *Reporter) Add(t report.TestResult) {
    25  	r.lock.Lock()
    26  	defer r.lock.Unlock()
    27  
    28  	// skip in-progress jobs
    29  	if !job.Done(t.Status) && !imagerunner.Done(t.Status) && !t.TimedOut {
    30  		return
    31  	}
    32  	// skip passed jobs
    33  	if t.Status == job.StatePassed || t.Status == imagerunner.StateSucceeded {
    34  		return
    35  	}
    36  
    37  	if t.TimedOut {
    38  		t.Status = job.StateUnknown
    39  	}
    40  
    41  	r.TestResults = append(r.TestResults, t)
    42  }
    43  
    44  // Render renders out a test summary.
    45  func (r *Reporter) Render() {
    46  	r.lock.Lock()
    47  	defer r.lock.Unlock()
    48  
    49  	r.println()
    50  	rl := color.New(color.FgBlue, color.Underline, color.Bold).Sprintf("Spotlight:")
    51  
    52  	if len(r.TestResults) == 0 {
    53  		r.printf("  %s Nothing stands out!\n", rl)
    54  		return
    55  	}
    56  
    57  	r.printf("  %s\n", rl)
    58  	r.println()
    59  
    60  	for _, ts := range r.TestResults {
    61  		r.println("", jobStatusSymbol(ts.Status), ts.Name)
    62  		r.println("   ● URL:", ts.URL)
    63  
    64  		var junitReports []junit.TestSuites
    65  		for _, attempt := range ts.Attempts {
    66  			junitReports = append(junitReports, attempt.TestSuites)
    67  		}
    68  		if len(junitReports) > 0 {
    69  			junitReport := junit.MergeReports(junitReports...)
    70  			testCases := junitReport.TestCases()
    71  
    72  			var failedTests []string
    73  			for _, tc := range testCases {
    74  				if tc.IsError() || tc.IsFailure() {
    75  					failedTests = append(failedTests, fmt.Sprintf("%s %s › %s", testCaseStatusSymbol(tc), tc.ClassName, tc.Name))
    76  				}
    77  				// only show the first 5 failed tests to conserve space
    78  				if len(failedTests) == 5 {
    79  					break
    80  				}
    81  			}
    82  
    83  			if len(failedTests) > 0 {
    84  				r.println("   ● Failed Tests: (showing max. 5)")
    85  				for _, test := range failedTests {
    86  					r.println("    ", test)
    87  				}
    88  			}
    89  			r.println()
    90  		}
    91  	}
    92  }
    93  
    94  func (r *Reporter) println(a ...any) {
    95  	_, _ = fmt.Fprintln(r.Dst, a...)
    96  }
    97  
    98  func (r *Reporter) printf(format string, a ...any) {
    99  	_, _ = fmt.Fprintf(r.Dst, format, a...)
   100  }
   101  
   102  // Reset resets the reporter to its initial state. This action will delete all test results.
   103  func (r *Reporter) Reset() {
   104  	r.lock.Lock()
   105  	defer r.lock.Unlock()
   106  	r.TestResults = make([]report.TestResult, 0)
   107  }
   108  
   109  // ArtifactRequirements returns a list of artifact types this reporter requires
   110  // to create a proper report.
   111  func (r *Reporter) ArtifactRequirements() []report.ArtifactType {
   112  	return []report.ArtifactType{report.JUnitArtifact}
   113  }
   114  
   115  func jobStatusSymbol(status string) string {
   116  	switch status {
   117  	case job.StatePassed, imagerunner.StateSucceeded:
   118  		return color.GreenString("✔")
   119  	case job.StateInProgress, job.StateQueued, job.StateNew, imagerunner.StateRunning, imagerunner.StatePending,
   120  		imagerunner.StateUploading:
   121  		return color.BlueString("*")
   122  	default:
   123  		return color.RedString("✖")
   124  	}
   125  }
   126  
   127  func testCaseStatusSymbol(tc junit.TestCase) string {
   128  	if tc.IsError() || tc.IsFailure() {
   129  		return color.RedString("✖")
   130  	}
   131  	if tc.IsSkipped() {
   132  		return color.YellowString("-")
   133  	}
   134  	return color.GreenString("✔")
   135  }