2424import com .google .common .flogger .FluentLogger ;
2525import com .google .devtools .mobileharness .api .model .error .MobileHarnessException ;
2626import com .google .devtools .mobileharness .api .model .job .out .Result .ResultTypeWithCause ;
27- import com .google .devtools .mobileharness .infra .client .api .controller .job .retry .strategy .FlakyTestRetryConstants ;
27+ import com .google .devtools .mobileharness .infra .client .api .controller .job .retry .processor .RetryTestsGrouper ;
28+ import com .google .devtools .mobileharness .infra .client .api .controller .job .retry .processor .RetryTestsGrouper .GroupedTests ;
29+ import com .google .devtools .mobileharness .infra .client .api .controller .job .retry .processor .RetryTestsGrouper .ShardTestRuns ;
2830import com .google .devtools .mobileharness .platform .android .instrumentation .result .proto .TestSuiteResult ;
2931import com .google .devtools .mobileharness .shared .util .file .local .LocalFileUtil ;
3032import com .google .devtools .mobileharness .shared .util .path .PathUtil ;
3133import com .google .devtools .mobileharness .shared .util .testresult .rollup .Outcome .OutcomeSummary ;
3234import com .google .protobuf .ExtensionRegistryLite ;
3335import com .google .protobuf .InvalidProtocolBufferException ;
34- import com .google .wireless .qa .mobileharness .shared .constant .PropertyName .Test ;
3536import com .google .wireless .qa .mobileharness .shared .model .job .JobInfo ;
3637import com .google .wireless .qa .mobileharness .shared .model .job .TestInfo ;
3738import java .util .ArrayList ;
38- import java .util .Comparator ;
3939import java .util .List ;
40- import java .util .Map ;
41- import java .util .Optional ;
42- import java .util .TreeMap ;
43- import java .util .stream .Collectors ;
4440
4541/** Utility for computing job-level rolled up test results. */
4642public final class JobResultUtil {
@@ -54,9 +50,8 @@ private JobResultUtil() {}
5450 * to get a rollup {@link TestResult} for each shard, and then rolling up shards horizontally.
5551 */
5652 public static TestResult computeJobRunResult (JobInfo jobInfo ) {
57- ImmutableListMultimap <String , TestInfo > groupedTestRunsByShard =
58- groupTestRunsByShard (ImmutableList .copyOf (jobInfo .tests ().getAll ().values ()));
59- if (groupedTestRunsByShard .isEmpty ()) {
53+ GroupedTests groupedTests = RetryTestsGrouper .groupTestsByShardAndFlakyAttemptIndex (jobInfo );
54+ if (groupedTests .testsByShardIndex ().isEmpty ()) {
6055 logger .atInfo ().log ("No valid test runs found for job %s." , jobInfo .locator ().getId ());
6156 return TestResult .create (
6257 /* testCases= */ ImmutableList .of (),
@@ -66,10 +61,9 @@ public static TestResult computeJobRunResult(JobInfo jobInfo) {
6661 }
6762
6863 ImmutableList .Builder <TestResult > shardResultsOfJobRunBuilder = ImmutableList .builder ();
69- for (String shardIndex : groupedTestRunsByShard .keySet ()) {
70- ImmutableList <TestInfo > attemptsForOneShard = groupedTestRunsByShard .get (shardIndex );
64+ for (ShardTestRuns attemptsForOneShard : groupedTests .testsByShardIndex ().values ()) {
7165 List <TestResult > rerunShardTestResults = new ArrayList <>();
72- for (TestInfo attempt : attemptsForOneShard ) {
66+ for (TestInfo attempt : attemptsForOneShard . testsByFlakyAttemptIndex (). values () ) {
7367 rerunShardTestResults .add (loadTestResult (attempt ));
7468 }
7569 TestResult rollupShardResult = rollupToShardTestResult (rerunShardTestResults );
@@ -80,75 +74,6 @@ public static TestResult computeJobRunResult(JobInfo jobInfo) {
8074 return rollupShardResultsToJobResult (shardResults );
8175 }
8276
83- /**
84- * Groups test runs by shard.
85- *
86- * <p>Each entry in the returned map represents all test runs(including original run and reruns,
87- * only consider pass or fail runs) for a particular shard. If none of the runs for a shard is
88- * pass or fail, return the last run for that shard.
89- */
90- private static ImmutableListMultimap <String , TestInfo > groupTestRunsByShard (
91- List <TestInfo > testInfoList ) {
92- ImmutableListMultimap <String , TestInfo > shardIndexToTestInfoList =
93- Multimaps .index (
94- testInfoList , t -> t .properties ().getOptional (Test .SHARD_INDEX ).orElse ("0" ));
95-
96- ImmutableListMultimap .Builder <String , TestInfo > testRunsByShard =
97- ImmutableListMultimap .builder ();
98-
99- for (String shardIndex : shardIndexToTestInfoList .keySet ()) {
100- ImmutableList <TestInfo > testRunsForShard = shardIndexToTestInfoList .get (shardIndex );
101- logger .atInfo ().log ("Found %s test runs for shard %s" , testRunsForShard .size (), shardIndex );
102- boolean hasPassOrFail = testRunsForShard .stream ().anyMatch (JobResultUtil ::isPassOrFail );
103-
104- if (hasPassOrFail ) {
105- Map <Integer , ImmutableList <TestInfo >> flakyAttemptIndexToTestInfoList =
106- testRunsForShard .stream ()
107- .collect (
108- Collectors .groupingBy (
109- t ->
110- getAttemptIndexProperty (
111- t , FlakyTestRetryConstants .TEST_PROP_FLAKY_ATTEMPT_INDEX ),
112- TreeMap ::new ,
113- toImmutableList ()));
114-
115- for (ImmutableList <TestInfo > testsFromSameFlakyAttempt :
116- flakyAttemptIndexToTestInfoList .values ()) {
117- findLastPassOrFailRun (testsFromSameFlakyAttempt )
118- .ifPresent (passOrFailTest -> testRunsByShard .put (shardIndex , passOrFailTest ));
119- }
120- } else {
121- findLastRun (testRunsForShard )
122- .ifPresent (lastRun -> testRunsByShard .put (shardIndex , lastRun ));
123- }
124- }
125-
126- return testRunsByShard .build ();
127- }
128-
129- private static Optional <TestInfo > findLastPassOrFailRun (List <TestInfo > attempts ) {
130- return attempts .stream ()
131- .filter (JobResultUtil ::isPassOrFail )
132- .max (
133- Comparator .comparingInt (
134- t ->
135- getAttemptIndexProperty (
136- t , FlakyTestRetryConstants .TEST_PROP_ERROR_ATTEMPT_INDEX )));
137- }
138-
139- private static Optional <TestInfo > findLastRun (List <TestInfo > attempts ) {
140- return attempts .stream ()
141- .max (
142- Comparator .comparingInt (
143- (TestInfo t ) ->
144- getAttemptIndexProperty (
145- t , FlakyTestRetryConstants .TEST_PROP_FLAKY_ATTEMPT_INDEX ))
146- .thenComparingInt (
147- t ->
148- getAttemptIndexProperty (
149- t , FlakyTestRetryConstants .TEST_PROP_ERROR_ATTEMPT_INDEX )));
150- }
151-
15277 private static boolean isPassOrFail (TestInfo testInfo ) {
15378 if (testInfo .resultWithCause () == null || testInfo .resultWithCause ().get () == null ) {
15479 return false ;
@@ -159,18 +84,6 @@ private static boolean isPassOrFail(TestInfo testInfo) {
15984 || type == com .google .devtools .mobileharness .api .model .proto .Test .TestResult .FAIL ;
16085 }
16186
162- private static int getAttemptIndexProperty (TestInfo testInfo , String propertyName ) {
163- String value = testInfo .properties ().get (propertyName );
164- if (value == null ) {
165- return 0 ;
166- }
167- try {
168- return Integer .parseInt (value );
169- } catch (NumberFormatException e ) {
170- return 0 ;
171- }
172- }
173-
17487 private static TestResult loadTestResult (TestInfo testInfo ) {
17588 if (!isPassOrFail (testInfo )) {
17689 return TestResult .create (
0 commit comments