Skip to content

Commit 7342a7a

Browse files
baishengzcopybara-github
authored andcommitted
Internal change
PiperOrigin-RevId: 948890303
1 parent 0b3fab1 commit 7342a7a

18 files changed

Lines changed: 1014 additions & 25 deletions

File tree

src/java/com/google/devtools/mobileharness/infra/client/api/controller/job/retry/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ java_library(
5858
":retry_strategy",
5959
"//src/devtools/mobileharness/api/model/proto:test_java_proto",
6060
"//src/java/com/google/devtools/mobileharness/api/model/error",
61+
"//src/java/com/google/devtools/mobileharness/infra/client/api/controller/job/retry/processor:test_retry_processor",
6162
"//src/java/com/google/devtools/mobileharness/shared/util/logging:google_logger",
6263
"//src/java/com/google/wireless/qa/mobileharness/shared/model/job",
6364
"@maven//:com_google_guava_guava",

src/java/com/google/devtools/mobileharness/infra/client/api/controller/job/retry/FlakyTestRetryStrategy.java

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616

1717
package com.google.devtools.mobileharness.infra.client.api.controller.job.retry;
1818

19+
import com.google.common.annotations.VisibleForTesting;
1920
import com.google.common.collect.ImmutableMap;
2021
import com.google.common.collect.ImmutableSet;
2122
import com.google.common.flogger.FluentLogger;
2223
import com.google.devtools.mobileharness.api.model.error.MobileHarnessException;
2324
import com.google.devtools.mobileharness.api.model.proto.Test.TestResult;
25+
import com.google.devtools.mobileharness.infra.client.api.controller.job.retry.processor.TestRetryProcessor;
2426
import com.google.wireless.qa.mobileharness.shared.model.job.TestInfo;
2527
import java.util.Optional;
2628

@@ -42,6 +44,17 @@ public class FlakyTestRetryStrategy implements RetryStrategy {
4244
static final ImmutableSet<TestResult> TEST_RESULTS_OF_ERROR_ATTEMPTS =
4345
ImmutableSet.of(TestResult.ERROR, TestResult.TIMEOUT, TestResult.UNKNOWN);
4446

47+
private final TestRetryProcessor testRetryProcessor;
48+
49+
public FlakyTestRetryStrategy() {
50+
this(new TestRetryProcessor());
51+
}
52+
53+
@VisibleForTesting
54+
FlakyTestRetryStrategy(TestRetryProcessor testRetryProcessor) {
55+
this.testRetryProcessor = testRetryProcessor;
56+
}
57+
4558
@Override
4659
public RetryInfo decideRetryOnTestEnd(TestInfo currentTestInfo)
4760
throws MobileHarnessException, InterruptedException {
@@ -85,11 +98,11 @@ public RetryInfo decideRetryOnTestEnd(TestInfo currentTestInfo)
8598
} else {
8699
return new RetryInfo(
87100
Optional.of("TEST_FAIL"),
88-
ImmutableMap.of(
89-
TEST_PROP_ERROR_ATTEMPT_INDEX,
90-
"0",
91-
TEST_PROP_FLAKY_ATTEMPT_INDEX,
92-
Integer.toString(currentFlakyAttemptIndex + 1)));
101+
ImmutableMap.<String, String>builder()
102+
.put(TEST_PROP_ERROR_ATTEMPT_INDEX, "0")
103+
.put(TEST_PROP_FLAKY_ATTEMPT_INDEX, Integer.toString(currentFlakyAttemptIndex + 1))
104+
.putAll(testRetryProcessor.generateRetryTestTargetsProperty(currentTestInfo))
105+
.buildOrThrow());
93106
}
94107
}
95108

@@ -101,9 +114,11 @@ public RetryInfo decideRetryOnTestEnd(TestInfo currentTestInfo)
101114
if (errorAttempts < MAX_ERROR_ATTEMPTS) {
102115
return new RetryInfo(
103116
Optional.of("TEST_" + testResult.name()),
104-
ImmutableMap.of(
105-
TEST_PROP_ERROR_ATTEMPT_INDEX, Integer.toString(errorAttempts),
106-
TEST_PROP_FLAKY_ATTEMPT_INDEX, Integer.toString(currentFlakyAttemptIndex)));
117+
ImmutableMap.<String, String>builder()
118+
.put(TEST_PROP_ERROR_ATTEMPT_INDEX, Integer.toString(errorAttempts))
119+
.put(TEST_PROP_FLAKY_ATTEMPT_INDEX, Integer.toString(currentFlakyAttemptIndex))
120+
.putAll(testRetryProcessor.generateRetryTestTargetsProperty(currentTestInfo))
121+
.buildOrThrow());
107122
}
108123
}
109124

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Copyright 2022 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.devtools.mobileharness.infra.client.api.controller.job.retry.processor;
18+
19+
import static com.google.common.collect.ImmutableList.toImmutableList;
20+
import static java.util.stream.Collectors.joining;
21+
22+
import com.google.common.annotations.VisibleForTesting;
23+
import com.google.common.base.Ascii;
24+
import com.google.common.collect.ImmutableList;
25+
import com.google.common.collect.ImmutableMap;
26+
import com.google.common.flogger.FluentLogger;
27+
import com.google.devtools.mobileharness.platform.android.instrumentation.result.TestSuiteResultLoader;
28+
import com.google.devtools.mobileharness.platform.android.instrumentation.result.proto.TestCase;
29+
import com.google.devtools.mobileharness.platform.android.instrumentation.result.proto.TestResult;
30+
import com.google.devtools.mobileharness.platform.android.instrumentation.result.proto.TestStatus;
31+
import com.google.devtools.mobileharness.platform.android.instrumentation.result.proto.TestSuiteResult;
32+
import com.google.wireless.qa.mobileharness.shared.constant.PropertyName.Test;
33+
import com.google.wireless.qa.mobileharness.shared.model.job.TestInfo;
34+
import java.util.Optional;
35+
36+
/** Processor to process Android instrumentation test retry. */
37+
class AndroidInstrumentationTestRetryProcessor {
38+
39+
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
40+
41+
private final TestSuiteResultLoader testSuiteResultLoader;
42+
43+
public AndroidInstrumentationTestRetryProcessor() {
44+
this(new TestSuiteResultLoader());
45+
}
46+
47+
@VisibleForTesting
48+
AndroidInstrumentationTestRetryProcessor(TestSuiteResultLoader testSuiteResultLoader) {
49+
this.testSuiteResultLoader = testSuiteResultLoader;
50+
}
51+
52+
/**
53+
* Generates properties (like failed test classes/methods to rerun) for retrying an Android
54+
* instrumentation test based on the execution result of the current test.
55+
*/
56+
public ImmutableMap<String, String> generateRetryTestTargetsProperty(TestInfo currentTestInfo) {
57+
com.google.devtools.mobileharness.api.model.proto.Test.TestResult currentTestResult =
58+
currentTestInfo.resultWithCause().get().type();
59+
if (currentTestResult
60+
!= com.google.devtools.mobileharness.api.model.proto.Test.TestResult.FAIL) {
61+
// If test result of current test is NOT fail but caller still wants to retry it, test
62+
// property ANDROID_INSTRUMENTATION_RETRY_TEST_TARGETS will be inherited from the current test
63+
// if any.
64+
Optional<String> retryTestTargets =
65+
currentTestInfo
66+
.properties()
67+
.getOptional(Test.AndroidInstrumentation.ANDROID_INSTRUMENTATION_RETRY_TEST_TARGETS);
68+
if (retryTestTargets.isPresent() && !retryTestTargets.get().isEmpty()) {
69+
return ImmutableMap.of(
70+
Ascii.toLowerCase(
71+
Test.AndroidInstrumentation.ANDROID_INSTRUMENTATION_RETRY_TEST_TARGETS.name()),
72+
retryTestTargets.get());
73+
}
74+
return ImmutableMap.of();
75+
}
76+
77+
Optional<TestSuiteResult> testSuiteResult =
78+
testSuiteResultLoader.loadTestResult(currentTestInfo);
79+
if (testSuiteResult.isEmpty()) {
80+
logger.atInfo().log(
81+
"No Android instrumentation test result found for test %s.",
82+
currentTestInfo.locator().getId());
83+
return ImmutableMap.of();
84+
}
85+
ImmutableList<TestCase> failedOrErrorTestCases =
86+
testSuiteResult.get().getTestResultList().stream()
87+
.filter(
88+
testResult ->
89+
testResult.getTestStatus() == TestStatus.FAILED
90+
|| testResult.getTestStatus() == TestStatus.ERROR)
91+
.map(TestResult::getTestCase)
92+
.collect(toImmutableList());
93+
if (failedOrErrorTestCases.isEmpty()) {
94+
logger.atInfo().log(
95+
"No failed or error test cases found for test %s.", currentTestInfo.locator().getId());
96+
return ImmutableMap.of();
97+
}
98+
String failedOrErrorTestTargets =
99+
failedOrErrorTestCases.stream()
100+
.map(
101+
testCase ->
102+
String.format(
103+
"%s.%s#%s",
104+
testCase.getTestPackage(),
105+
testCase.getTestClass(),
106+
testCase.getTestMethod()))
107+
.collect(joining(","));
108+
return ImmutableMap.of(
109+
Ascii.toLowerCase(
110+
Test.AndroidInstrumentation.ANDROID_INSTRUMENTATION_RETRY_TEST_TARGETS.name()),
111+
failedOrErrorTestTargets);
112+
}
113+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Copyright 2022 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
16+
load("@rules_java//java:defs.bzl", "java_library")
17+
18+
package(
19+
default_applicable_licenses = ["//:license"],
20+
default_visibility = [
21+
"//:deviceinfra_all_pkg",
22+
],
23+
)
24+
25+
java_library(
26+
name = "test_retry_processor",
27+
srcs = ["TestRetryProcessor.java"],
28+
deps = [
29+
":android_instrumentation_test_retry_processor",
30+
"//src/java/com/google/devtools/mobileharness/shared/util/logging:google_logger",
31+
"//src/java/com/google/wireless/qa/mobileharness/shared/api/spec:android_instrumentation_driver_spec",
32+
"//src/java/com/google/wireless/qa/mobileharness/shared/model/job",
33+
"@maven//:com_google_guava_guava",
34+
],
35+
)
36+
37+
java_library(
38+
name = "android_instrumentation_test_retry_processor",
39+
srcs = ["AndroidInstrumentationTestRetryProcessor.java"],
40+
deps = [
41+
"//src/devtools/mobileharness/api/model/proto:test_java_proto",
42+
"//src/devtools/mobileharness/platform/android/instrumentation/result/proto:test_suite_result_java_proto",
43+
"//src/java/com/google/devtools/mobileharness/platform/android/instrumentation/result:test_suite_result_loader",
44+
"//src/java/com/google/devtools/mobileharness/shared/util/logging:google_logger",
45+
"//src/java/com/google/wireless/qa/mobileharness/shared/constant:property",
46+
"//src/java/com/google/wireless/qa/mobileharness/shared/model/job",
47+
"@maven//:com_google_guava_guava",
48+
],
49+
)
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2022 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.devtools.mobileharness.infra.client.api.controller.job.retry.processor;
18+
19+
import com.google.common.annotations.VisibleForTesting;
20+
import com.google.common.collect.ImmutableMap;
21+
import com.google.common.flogger.FluentLogger;
22+
import com.google.wireless.qa.mobileharness.shared.api.spec.AndroidInstrumentationDriverSpec;
23+
import com.google.wireless.qa.mobileharness.shared.model.job.TestInfo;
24+
25+
/** Processor to process test retry. */
26+
public class TestRetryProcessor {
27+
28+
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
29+
30+
private final AndroidInstrumentationTestRetryProcessor androidInstrumentationTestRetryProcessor;
31+
32+
public TestRetryProcessor() {
33+
this(new AndroidInstrumentationTestRetryProcessor());
34+
}
35+
36+
@VisibleForTesting
37+
TestRetryProcessor(
38+
AndroidInstrumentationTestRetryProcessor androidInstrumentationTestRetryProcessor) {
39+
this.androidInstrumentationTestRetryProcessor = androidInstrumentationTestRetryProcessor;
40+
}
41+
42+
/**
43+
* Generates properties (like retry test targets) for a retry test based on the execution result
44+
* of the current test.
45+
*/
46+
public ImmutableMap<String, String> generateRetryTestTargetsProperty(TestInfo currentTestInfo) {
47+
String driver = currentTestInfo.jobInfo().type().getDriver();
48+
// Currently only allow Android instrumentation failed-tests-only retry when the param is
49+
// explicitly set.
50+
boolean enableAndroidInstrumentationFailedTestsOnlyRetry =
51+
currentTestInfo
52+
.jobInfo()
53+
.params()
54+
.getBool(
55+
AndroidInstrumentationDriverSpec
56+
.PARAM_ENABLE_ANDROID_INSTRUMENTATION_FAILED_TESTS_ONLY_RETRY,
57+
false);
58+
if (enableAndroidInstrumentationFailedTestsOnlyRetry
59+
&& driver.equals("AndroidInstrumentation")) {
60+
logger.atInfo().log(
61+
"Processing test retry for test %s running with AndroidInstrumentation.",
62+
currentTestInfo.locator().getId());
63+
return androidInstrumentationTestRetryProcessor.generateRetryTestTargetsProperty(
64+
currentTestInfo);
65+
}
66+
return ImmutableMap.of();
67+
}
68+
}

src/java/com/google/devtools/mobileharness/platform/android/instrumentation/result/BUILD

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,18 @@ java_library(
3636
"@protobuf//:protobuf_java_util",
3737
],
3838
)
39+
40+
java_library(
41+
name = "test_suite_result_loader",
42+
srcs = ["TestSuiteResultLoader.java"],
43+
deps = [
44+
"//src/devtools/mobileharness/platform/android/instrumentation/result/proto:test_suite_result_java_proto",
45+
"//src/java/com/google/devtools/mobileharness/api/model/error",
46+
"//src/java/com/google/devtools/mobileharness/shared/util/file/local",
47+
"//src/java/com/google/devtools/mobileharness/shared/util/logging:google_logger",
48+
"//src/java/com/google/devtools/mobileharness/shared/util/path",
49+
"//src/java/com/google/wireless/qa/mobileharness/shared/model/job",
50+
"@maven//:com_google_guava_guava",
51+
"@protobuf//:protobuf_java",
52+
],
53+
)
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2022 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.devtools.mobileharness.platform.android.instrumentation.result;
18+
19+
import com.google.common.annotations.VisibleForTesting;
20+
import com.google.common.flogger.FluentLogger;
21+
import com.google.devtools.mobileharness.api.model.error.MobileHarnessException;
22+
import com.google.devtools.mobileharness.platform.android.instrumentation.result.proto.TestSuiteResult;
23+
import com.google.devtools.mobileharness.shared.util.file.local.LocalFileUtil;
24+
import com.google.devtools.mobileharness.shared.util.path.PathUtil;
25+
import com.google.protobuf.ExtensionRegistryLite;
26+
import com.google.protobuf.InvalidProtocolBufferException;
27+
import com.google.wireless.qa.mobileharness.shared.model.job.TestInfo;
28+
import java.util.Optional;
29+
30+
/** Loader for Android instrumentation test result. */
31+
public class TestSuiteResultLoader {
32+
33+
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
34+
35+
private final LocalFileUtil localFileUtil;
36+
37+
public TestSuiteResultLoader() {
38+
this(new LocalFileUtil());
39+
}
40+
41+
@VisibleForTesting
42+
TestSuiteResultLoader(LocalFileUtil localFileUtil) {
43+
this.localFileUtil = localFileUtil;
44+
}
45+
46+
public Optional<TestSuiteResult> loadTestResult(TestInfo testInfo) {
47+
try {
48+
String genFileDir = testInfo.getGenFileDir();
49+
String pbPath = PathUtil.join(genFileDir, "instrument_test_result.pb");
50+
if (localFileUtil.isFileExist(pbPath)) {
51+
byte[] bytes = localFileUtil.readBinaryFile(pbPath);
52+
return Optional.of(
53+
TestSuiteResult.parseFrom(bytes, ExtensionRegistryLite.getEmptyRegistry()));
54+
} else {
55+
logger.atInfo().log(
56+
"No instrument_test_result.pb found for test %s.", testInfo.locator().getId());
57+
}
58+
} catch (InvalidProtocolBufferException | MobileHarnessException e) {
59+
logger.atWarning().withCause(e).log(
60+
"Failed to load test result for test %s.", testInfo.locator().getId());
61+
}
62+
return Optional.empty();
63+
}
64+
}

0 commit comments

Comments
 (0)