Skip to content

Commit

Permalink
fix: Should download artifacts when no retries (#943)
Browse files Browse the repository at this point in the history
* fix: Should download artifacts when no retries

* revise comments

* revise logic

* revise shouldRetry

* We've known if it's the last attemp before downloading

* fix unit test

* revise downloader

* add ut for skipDownload
  • Loading branch information
tianfeng92 authored Sep 3, 2024
1 parent c5e0b4c commit e5d2ff4
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 21 deletions.
2 changes: 1 addition & 1 deletion internal/job/job.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,5 +93,5 @@ type Service interface {
// ArtifactDownloader represents the interface for downloading artifacts.
type ArtifactDownloader interface {
// DownloadArtifact downloads artifacts and returns a list of what was downloaded.
DownloadArtifact(job Job, attempt int, retries int) []string
DownloadArtifact(job Job, isLastAttempt bool) []string
}
6 changes: 3 additions & 3 deletions internal/mocks/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import "github.com/saucelabs/saucectl/internal/job"

// FakeArtifactDownloader defines a fake Downloader
type FakeArtifactDownloader struct {
DownloadArtifactFn func(jobData job.Job, attempt int, retries int) []string
DownloadArtifactFn func(jobData job.Job, isLastAttempt bool) []string
}

// DownloadArtifact defines a fake function for FakeDownloader
func (f *FakeArtifactDownloader) DownloadArtifact(jobData job.Job, attempt int, retries int) []string {
return f.DownloadArtifactFn(jobData, attempt, retries)
func (f *FakeArtifactDownloader) DownloadArtifact(jobData job.Job, isLastAttempt bool) []string {
return f.DownloadArtifactFn(jobData, isLastAttempt)
}
26 changes: 23 additions & 3 deletions internal/saucecloud/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,26 @@ func (r *CloudRunner) runJob(opts job.StartOptions) (j job.Job, skipped bool, er
return j, false, nil
}

func belowRetryLimit(opts job.StartOptions) bool {
return opts.Attempt < opts.Retries
}

func belowThreshold(opts job.StartOptions) bool {
return opts.CurrentPassCount < opts.PassThreshold
}

// shouldRetryJob checks if the job should be retried,
// based on whether it passed and if it was skipped.
func shouldRetryJob(jobData job.Job, skipped bool) bool {
return !jobData.Passed && !skipped
}

// shouldRetry determines whether a job should be retried.
func shouldRetry(opts job.StartOptions, jobData job.Job, skipped bool) bool {
return belowRetryLimit(opts) &&
(shouldRetryJob(jobData, skipped) || belowThreshold(opts))
}

func (r *CloudRunner) runJobs(jobOpts chan job.StartOptions, results chan<- result) {
for opts := range jobOpts {
start := time.Now()
Expand Down Expand Up @@ -333,8 +353,8 @@ func (r *CloudRunner) runJobs(jobOpts chan job.StartOptions, results chan<- resu
opts.CurrentPassCount++
}

if opts.Attempt < opts.Retries && ((!jobData.Passed && !skipped) || (opts.CurrentPassCount < opts.PassThreshold)) {
go r.JobService.DownloadArtifact(jobData, opts.Attempt, opts.Retries)
if shouldRetry(opts, jobData, skipped) {
go r.JobService.DownloadArtifact(jobData, false)
if !jobData.Passed {
log.Warn().Err(err).Msg("Suite errored.")
}
Expand Down Expand Up @@ -370,7 +390,7 @@ func (r *CloudRunner) runJobs(jobOpts chan job.StartOptions, results chan<- resu
}
}

files := r.JobService.DownloadArtifact(jobData, opts.Attempt, opts.Retries)
files := r.JobService.DownloadArtifact(jobData, true)
var artifacts []report.Artifact
for _, f := range files {
artifacts = append(artifacts, report.Artifact{
Expand Down
12 changes: 6 additions & 6 deletions internal/saucecloud/cloud_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,10 @@ func TestRunJobTimeout(t *testing.T) {
VDCWriter: &mocks.FakeJobWriter{UploadAssetFn: func(jobID string, fileName string, contentType string, content []byte) error {
return nil
}},
VDCDownloader: &mocks.FakeArtifactDownloader{DownloadArtifactFn: func(jobData job.Job, attempt int, retries int) []string {
VDCDownloader: &mocks.FakeArtifactDownloader{DownloadArtifactFn: func(jobData job.Job, isLastAttempt bool) []string {
return []string{}
}},
RDCDownloader: &mocks.FakeArtifactDownloader{DownloadArtifactFn: func(jobData job.Job, attempt int, retries int) []string {
RDCDownloader: &mocks.FakeArtifactDownloader{DownloadArtifactFn: func(jobData job.Job, isLastAttempt bool) []string {
return []string{}
}},
},
Expand Down Expand Up @@ -227,10 +227,10 @@ func TestRunJobRetries(t *testing.T) {
VDCWriter: &mocks.FakeJobWriter{UploadAssetFn: func(jobID string, fileName string, contentType string, content []byte) error {
return nil
}},
VDCDownloader: &mocks.FakeArtifactDownloader{DownloadArtifactFn: func(jobData job.Job, attempt int, retries int) []string {
VDCDownloader: &mocks.FakeArtifactDownloader{DownloadArtifactFn: func(jobData job.Job, islastAttempt bool) []string {
return []string{}
}},
RDCDownloader: &mocks.FakeArtifactDownloader{DownloadArtifactFn: func(jobData job.Job, attempt int, retries int) []string {
RDCDownloader: &mocks.FakeArtifactDownloader{DownloadArtifactFn: func(jobData job.Job, islastAttempt bool) []string {
return []string{}
}},
},
Expand Down Expand Up @@ -269,10 +269,10 @@ func TestRunJobTimeoutRDC(t *testing.T) {
return job.Job{ID: id, TimedOut: true}, nil
},
},
VDCDownloader: &mocks.FakeArtifactDownloader{DownloadArtifactFn: func(jobData job.Job, attempt int, retries int) []string {
VDCDownloader: &mocks.FakeArtifactDownloader{DownloadArtifactFn: func(jobData job.Job, islastAttempt bool) []string {
return []string{}
}},
RDCDownloader: &mocks.FakeArtifactDownloader{DownloadArtifactFn: func(jobData job.Job, attempt int, retries int) []string {
RDCDownloader: &mocks.FakeArtifactDownloader{DownloadArtifactFn: func(jobData job.Job, islastAttempt bool) []string {
return []string{}
}},
},
Expand Down
2 changes: 1 addition & 1 deletion internal/saucecloud/cypress_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func TestRunSuites(t *testing.T) {
},
},
VDCDownloader: &mocks.FakeArtifactDownloader{
DownloadArtifactFn: func(jobData job.Job, attempt int, retries int) []string {
DownloadArtifactFn: func(jobData job.Job, islastAttempt bool) []string {
return []string{}
},
},
Expand Down
10 changes: 7 additions & 3 deletions internal/saucecloud/downloader/downloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,15 @@ func NewArtifactDownloader(reader job.Reader, artifactConfig config.ArtifactDown
}
}

func (d *ArtifactDownloader) DownloadArtifact(jobData job.Job, attemptNumber int, retries int) []string {
if jobData.ID == "" ||
func (d *ArtifactDownloader) skipDownload(jobData job.Job, isLastAttempt bool) bool {
return jobData.ID == "" ||
jobData.TimedOut || !job.Done(jobData.Status) ||
!d.config.When.IsNow(jobData.Passed) ||
(!d.config.AllAttempts && attemptNumber < retries) {
(!isLastAttempt && !d.config.AllAttempts)
}

func (d *ArtifactDownloader) DownloadArtifact(jobData job.Job, isLastAttempt bool) []string {
if d.skipDownload(jobData, isLastAttempt) {
return []string{}
}

Expand Down
72 changes: 71 additions & 1 deletion internal/saucecloud/downloader/downloader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/saucelabs/saucectl/internal/config"
httpServices "github.com/saucelabs/saucectl/internal/http"
"github.com/saucelabs/saucectl/internal/job"
"gotest.tools/v3/assert"
)

func TestArtifactDownloader_DownloadArtifact(t *testing.T) {
Expand Down Expand Up @@ -53,7 +54,7 @@ func TestArtifactDownloader_DownloadArtifact(t *testing.T) {
Name: "suite name",
IsRDC: true,
Status: job.StateComplete,
}, 0, 0,
}, true,
)

fileName := filepath.Join(tempDir, "suite_name", "junit.xml")
Expand All @@ -66,3 +67,72 @@ func TestArtifactDownloader_DownloadArtifact(t *testing.T) {
t.Errorf("file content mismatch: got '%v', expects: '%v'", d, fileContent)
}
}

func TestSkipDownload(t *testing.T) {
testcases := []struct {
name string
config config.ArtifactDownload
jobData job.Job
isLastAttempt bool
expResult bool
}{
{
name: "Should not skip download",
config: config.ArtifactDownload{When: config.WhenAlways},
jobData: job.Job{ID: "fake-id", Status: job.StatePassed, Passed: true},
isLastAttempt: true,
expResult: false,
},
{
name: "Should skip download when job ID is empty",
config: config.ArtifactDownload{When: config.WhenAlways},
jobData: job.Job{Status: job.StatePassed, Passed: true},
isLastAttempt: true,
expResult: true,
},
{
name: "Should skip download when job is timeout",
config: config.ArtifactDownload{When: config.WhenAlways},
jobData: job.Job{ID: "fake-id", TimedOut: true},
isLastAttempt: true,
expResult: true,
},
{
name: "Should skip download when job is not done",
config: config.ArtifactDownload{When: config.WhenAlways},
jobData: job.Job{ID: "fake-id", TimedOut: true, Status: job.StateInProgress},
isLastAttempt: true,
expResult: true,
},
{
name: "Should skip download when artifact config is not set to download",
config: config.ArtifactDownload{When: config.WhenNever},
jobData: job.Job{ID: "fake-id", Status: job.StatePassed, Passed: true},
isLastAttempt: true,
expResult: true,
},
{
name: "Should skip download when it's not last attempt and not set download all attempts",
config: config.ArtifactDownload{When: config.WhenAlways, AllAttempts: false},
jobData: job.Job{ID: "fake-id", Status: job.StatePassed, Passed: true},
isLastAttempt: false,
expResult: true,
},
{
name: "Should download when it's the last attempt and not set download all attempts",
config: config.ArtifactDownload{When: config.WhenAlways, AllAttempts: false},
jobData: job.Job{ID: "fake-id", Status: job.StatePassed, Passed: true},
isLastAttempt: true,
expResult: false,
},
}

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
downloader := &ArtifactDownloader{config: tc.config}
got := downloader.skipDownload(tc.jobData, tc.isLastAttempt)
assert.Equal(t, tc.expResult, got)
})
}

}
6 changes: 3 additions & 3 deletions internal/saucecloud/jobservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ type JobService struct {
RDCDownloader job.ArtifactDownloader
}

func (s JobService) DownloadArtifact(jobData job.Job, attemptNumber int, retries int) []string {
func (s JobService) DownloadArtifact(jobData job.Job, isLastAttempt bool) []string {
if jobData.IsRDC {
return s.RDCDownloader.DownloadArtifact(jobData, attemptNumber, retries)
return s.RDCDownloader.DownloadArtifact(jobData, isLastAttempt)
}

return s.VDCDownloader.DownloadArtifact(jobData, attemptNumber, retries)
return s.VDCDownloader.DownloadArtifact(jobData, isLastAttempt)
}

func (s JobService) StopJob(ctx context.Context, jobID string, realDevice bool) (job.Job, error) {
Expand Down

0 comments on commit e5d2ff4

Please sign in to comment.