Skip to content

Commit 5a3c78c

Browse files
tianfeng92Alex Plischke
and
Alex Plischke
authored
fix: Fix smart retry in XCUITest simulator test (#873)
* fix: Fix smart retry for XCUITest simulator test --------- Co-authored-by: Alex Plischke <[email protected]>
1 parent c95eb7b commit 5a3c78c

File tree

3 files changed

+69
-10
lines changed

3 files changed

+69
-10
lines changed

internal/cmd/run/xcuitest.go

+1
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ func runXcuitestInCloud(p xcuitest.Project, regio region.Region) (int, error) {
174174
Async: gFlags.async,
175175
FailFast: gFlags.failFast,
176176
Retrier: &retry.JunitRetrier{
177+
VDCReader: &restoClient,
177178
RDCReader: &rdcClient,
178179
},
179180
},

internal/saucecloud/retry/junitretrier.go

+32-9
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package retry
33
import (
44
"context"
55
"fmt"
6+
"strings"
67

78
"github.com/rs/zerolog/log"
89
"github.com/saucelabs/saucectl/internal/job"
@@ -39,22 +40,30 @@ func (b *JunitRetrier) retryFailedTests(reader job.Reader, jobOpts chan<- job.St
3940

4041
// setClassesToRetry sets the correct filtering flag when retrying.
4142
// RDC API does not provide different endpoints (or identical values) for Espresso
42-
// and XCUITest. Thus, we need set the classes at the correct position depending the
43-
// framework that is being executed.
43+
// and XCUITest. Thus, we need set the classes at the correct position depending
44+
// on the framework that is being executed.
4445
func setClassesToRetry(opt *job.StartOptions, testcases []junit.TestCase) {
4546
lg := log.Info().
4647
Str("suite", opt.DisplayName).
4748
Str("attempt", fmt.Sprintf("%d of %d", opt.Attempt+1, opt.Retries+1))
4849

50+
if opt.TestOptions == nil {
51+
opt.TestOptions = map[string]interface{}{}
52+
}
53+
4954
if opt.Framework == xcuitest.Kind {
50-
opt.TestsToRun = getFailedXCUITests(testcases)
51-
lg.Msgf(msg.RetryWithTests, opt.TestsToRun)
55+
tests := getFailedXCUITests(testcases)
56+
57+
// RDC and VDC API filter use different fields for test filtering.
58+
if opt.RealDevice {
59+
opt.TestsToRun = tests
60+
} else {
61+
opt.TestOptions["class"] = tests
62+
}
63+
lg.Msgf(msg.RetryWithTests, tests)
5264
return
5365
}
5466

55-
if opt.TestOptions == nil {
56-
opt.TestOptions = map[string]interface{}{}
57-
}
5867
tests := getFailedEspressoTests(testcases)
5968
opt.TestOptions["class"] = tests
6069
lg.Msgf(msg.RetryWithTests, tests)
@@ -84,16 +93,30 @@ func getFailedXCUITests(testCases []junit.TestCase) []string {
8493
classes := map[string]bool{}
8594
for _, tc := range testCases {
8695
if tc.Error != nil || tc.Failure != nil {
96+
className := normalizeXCUITestClassName(tc.ClassName)
8797
if tc.Name != "" {
88-
classes[fmt.Sprintf("%s/%s", tc.ClassName, tc.Name)] = true
98+
classes[fmt.Sprintf("%s/%s", className, tc.Name)] = true
8999
} else {
90-
classes[tc.ClassName] = true
100+
classes[className] = true
91101
}
92102
}
93103
}
94104
return maps.Keys(classes)
95105
}
96106

107+
// normalizeXCUITestClassName normalizes the class name of an XCUITest. The
108+
// class name within the platform generated JUnit XML file can be dot-separated,
109+
// but our platform API expects a slash-separated class name. The platform is
110+
// unfortunately not consistent in this regard and is not in full control of the
111+
// generated JUnit XML file, hence we reconcile the two here.
112+
func normalizeXCUITestClassName(name string) string {
113+
items := strings.Split(name, ".")
114+
if len(items) == 1 {
115+
return name
116+
}
117+
return strings.Join(items, "/")
118+
}
119+
97120
// getFailedEspressoTests returns a list of failed Espresso tests from the given
98121
// test cases. The format is "<className>#<testMethodName>", with the test
99122
// method name being optional.

internal/saucecloud/retry/junitretrier_test.go

+36-1
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ func TestAppsRetrier_Retry(t *testing.T) {
185185
SmartRetry: job.SmartRetry{
186186
FailedOnly: true,
187187
},
188+
RealDevice: true,
188189
},
189190
previous: job.Job{
190191
ID: "fake-job-id",
@@ -194,10 +195,12 @@ func TestAppsRetrier_Retry(t *testing.T) {
194195
expected: job.StartOptions{
195196
Framework: xcuitest.Kind,
196197
DisplayName: "Dummy Test",
197-
TestsToRun: []string{"Demo.Class1/demoTest"},
198+
TestOptions: map[string]interface{}{},
199+
TestsToRun: []string{"Demo/Class1/demoTest"},
198200
SmartRetry: job.SmartRetry{
199201
FailedOnly: true,
200202
},
203+
RealDevice: true,
201204
},
202205
},
203206
{
@@ -370,3 +373,35 @@ func TestAppsRetrier_Retry(t *testing.T) {
370373
})
371374
}
372375
}
376+
377+
func Test_normalizeXCUITestClassName(t *testing.T) {
378+
type args struct {
379+
name string
380+
}
381+
tests := []struct {
382+
name string
383+
args args
384+
want string
385+
}{
386+
{
387+
name: "needs normalization",
388+
args: args{name: "DemoAppTests.ClassyTest"},
389+
want: "DemoAppTests/ClassyTest",
390+
},
391+
{
392+
name: "already normalized",
393+
args: args{name: "DemoAppTests/ClassyTest"},
394+
want: "DemoAppTests/ClassyTest",
395+
},
396+
{
397+
name: "nothing to normalize",
398+
args: args{name: "DemoAppTests"},
399+
want: "DemoAppTests",
400+
},
401+
}
402+
for _, tt := range tests {
403+
t.Run(tt.name, func(t *testing.T) {
404+
assert.Equalf(t, tt.want, normalizeXCUITestClassName(tt.args.name), "normalizeXCUITestClassName(%v)", tt.args.name)
405+
})
406+
}
407+
}

0 commit comments

Comments
 (0)