1 /*
2  * Copyright (C) 2020 The Android Open Source Project
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  *      http://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 package com.android.tradefed.result;
17 
18 import com.android.tradefed.result.error.ErrorIdentifier;
19 import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
20 
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.List;
24 
25 import javax.annotation.Nullable;
26 
27 /**
28  * Collect multiple {@link FailureDescription} in one holder. This can be used to carry all the
29  * failures description when several attempts on the same test case or run are made, each resulting
30  * in a failure.
31  */
32 public final class MultiFailureDescription extends FailureDescription {
33 
34     private List<FailureDescription> mFailures = new ArrayList<>();
35 
MultiFailureDescription(List<FailureDescription> failures)36     public MultiFailureDescription(List<FailureDescription> failures) {
37         super();
38         addMultiFailures(failures);
39     }
40 
MultiFailureDescription(FailureDescription... failures)41     public MultiFailureDescription(FailureDescription... failures) {
42         this(Arrays.asList(failures));
43     }
44 
45     /**
46      * Add another failure to an existing {@link MultiFailureDescription}.
47      *
48      * @param failure The additional failure
49      * @return The current {@link MultiFailureDescription}.
50      */
addFailure(FailureDescription failure)51     public MultiFailureDescription addFailure(FailureDescription failure) {
52         if (failure instanceof MultiFailureDescription) {
53             addMultiFailures(((MultiFailureDescription) failure).getFailures());
54         } else {
55             mFailures.add(failure);
56         }
57         return this;
58     }
59 
60     /**
61      * Returns the list of {@link FailureDescription} tracked by the {@link
62      * MultiFailureDescription}.
63      */
getFailures()64     public List<FailureDescription> getFailures() {
65         return mFailures;
66     }
67 
68     @Override
getFailureStatus()69     public @Nullable FailureStatus getFailureStatus() {
70         if (mFailures.isEmpty()) {
71             return null;
72         }
73         // Default to the first reported failure
74         return mFailures.get(0).getFailureStatus();
75     }
76 
77     @Override
getErrorMessage()78     public String getErrorMessage() {
79         if (mFailures.isEmpty()) {
80             return null;
81         }
82         // Default to the first reported failure
83         return toString();
84     }
85 
86     @Override
getErrorIdentifier()87     public ErrorIdentifier getErrorIdentifier() {
88         if (mFailures.isEmpty()) {
89             return null;
90         }
91         // Default to the first reported failure
92         return mFailures.get(0).getErrorIdentifier();
93     }
94 
95     @Override
getOrigin()96     public String getOrigin() {
97         if (mFailures.isEmpty()) {
98             return null;
99         }
100         // Default to the first reported failure
101         return mFailures.get(0).getOrigin();
102     }
103 
104     @Override
isRetriable()105     public boolean isRetriable() {
106         for (FailureDescription desc : mFailures) {
107             if (desc.isRetriable()) {
108                 return true;
109             }
110         }
111         // If none of the sub-failures are retriable, don't retry.
112         return false;
113     }
114 
115     @Override
toString()116     public String toString() {
117         // Fallback to Single failure type
118         if (mFailures.size() == 1) {
119             return mFailures.get(0).toString();
120         }
121         StringBuilder sb =
122                 new StringBuilder(String.format("There were %d failures:", mFailures.size()));
123         for (FailureDescription f : mFailures) {
124             sb.append(String.format("\n  %s", f.toString()));
125         }
126         return sb.toString();
127     }
128 
129     @Override
equals(Object obj)130     public boolean equals(Object obj) {
131         if (this == obj) return true;
132         if (obj == null) return false;
133         if (getClass() != obj.getClass()) return false;
134         MultiFailureDescription other = (MultiFailureDescription) obj;
135         if (other.mFailures.size() != this.mFailures.size()) {
136             return false;
137         }
138         for (int i = 0; i < this.mFailures.size(); i++) {
139             if (!this.mFailures.get(i).equals(other.mFailures.get(i))) {
140                 return false;
141             }
142         }
143         return true;
144     }
145 
146     /** Un-nest all the sub-MultiFailureDescription for ease of tracking. */
addMultiFailures(List<FailureDescription> failures)147     private void addMultiFailures(List<FailureDescription> failures) {
148         for (FailureDescription failure : failures) {
149             if (failure instanceof MultiFailureDescription) {
150                 addMultiFailures(((MultiFailureDescription) failure).getFailures());
151             } else {
152                 mFailures.add(failure);
153             }
154         }
155     }
156 }
157