• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.skipped;
17 import com.android.tradefed.config.IConfiguration;
18 import com.android.tradefed.config.IConfigurationReceiver;
19 import com.android.tradefed.invoker.InvocationContext;
20 import com.android.tradefed.invoker.TestInformation;
21 import com.android.tradefed.log.LogUtil.CLog;
22 import com.android.tradefed.service.IRemoteFeature;
23 import com.android.tradefed.service.TradefedFeatureClient;
24 import com.android.tradefed.testtype.ITestInformationReceiver;
25 
26 import build.bazel.remote.execution.v2.Digest;
27 
28 import com.google.common.base.Joiner;
29 import com.google.common.base.Strings;
30 import com.google.protobuf.InvalidProtocolBufferException;
31 import com.proto.tradefed.feature.ErrorInfo;
32 import com.proto.tradefed.feature.FeatureRequest;
33 import com.proto.tradefed.feature.FeatureResponse;
34 import com.proto.tradefed.feature.MultiPartResponse;
35 import com.proto.tradefed.feature.PartResponse;
36 
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.HashMap;
40 import java.util.HashSet;
41 import java.util.LinkedHashMap;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Map.Entry;
45 import java.util.Set;
46 
47 /** A feature allowing to access some of the {@link SkipManager} information. */
48 public class SkipFeature
49         implements IRemoteFeature, IConfigurationReceiver, ITestInformationReceiver {
50     public static final String SKIP_FEATURE = "skipFeature";
51     public static final String SKIPPED_MODULES = "skipModules";
52     public static final String IMAGE_DIGESTS = "imageDigests";
53     public static final String PRESUBMIT = "presubmit";
54     public static final String DELIMITER_NAME = "delimiter";
55     private static final String DELIMITER = "+,";
56     private static final String ESCAPED_DELIMITER = "\\+,";
57     private IConfiguration mConfig;
58     private TestInformation mInfo;
59 
60     @Override
setConfiguration(IConfiguration configuration)61     public void setConfiguration(IConfiguration configuration) {
62         mConfig = configuration;
63     }
64 
65     @Override
setTestInformation(TestInformation testInformation)66     public void setTestInformation(TestInformation testInformation) {
67         mInfo = testInformation;
68     }
69 
70     @Override
getTestInformation()71     public TestInformation getTestInformation() {
72         return mInfo;
73     }
74 
75     @Override
getName()76     public String getName() {
77         return SKIP_FEATURE;
78     }
79 
80     @Override
execute(FeatureRequest request)81     public FeatureResponse execute(FeatureRequest request) {
82         FeatureResponse.Builder responseBuilder = FeatureResponse.newBuilder();
83         if (mConfig != null) {
84             boolean presubmit = InvocationContext.isPresubmit(mInfo.getContext());
85             MultiPartResponse.Builder multiPartBuilder = MultiPartResponse.newBuilder();
86             multiPartBuilder.addResponsePart(
87                     PartResponse.newBuilder().setKey(DELIMITER_NAME).setValue(ESCAPED_DELIMITER));
88             multiPartBuilder.addResponsePart(
89                     PartResponse.newBuilder()
90                             .setKey(PRESUBMIT)
91                             .setValue(Boolean.toString(presubmit)));
92             multiPartBuilder.addResponsePart(
93                     PartResponse.newBuilder()
94                             .setKey(SKIPPED_MODULES)
95                             .setValue(
96                                     Joiner.on(DELIMITER)
97                                             .join(mConfig.getSkipManager().getUnchangedModules())));
98             multiPartBuilder.addResponsePart(
99                     PartResponse.newBuilder()
100                             .setKey(IMAGE_DIGESTS)
101                             .setValue(
102                                     Joiner.on(DELIMITER)
103                                             .join(
104                                                     serializeDigest(
105                                                             mConfig.getSkipManager()
106                                                                     .getImageToDigest()))));
107             responseBuilder.setMultiPartResponse(multiPartBuilder);
108         } else {
109             responseBuilder.setErrorInfo(
110                     ErrorInfo.newBuilder().setErrorTrace("Configuration not set."));
111         }
112         return responseBuilder.build();
113     }
114 
115     /** Fetch and populate unchanged modules if needed. */
getSkipContext()116     public static SkipContext getSkipContext() {
117         boolean isPresubmit = false;
118         Set<String> unchangedModulesSet = new HashSet<>();
119         Map<String, Digest> imageToDigestMap = new LinkedHashMap<String, Digest>();
120         try (TradefedFeatureClient client = new TradefedFeatureClient()) {
121             FeatureResponse unchangedModules =
122                     client.triggerFeature(SkipFeature.SKIP_FEATURE, new HashMap<String, String>());
123             if (unchangedModules.hasMultiPartResponse()) {
124                 String delimiter = DELIMITER;
125                 for (PartResponse rep :
126                         unchangedModules.getMultiPartResponse().getResponsePartList()) {
127                     if (rep.getKey().equals(DELIMITER_NAME)) {
128                         delimiter = rep.getValue().trim();
129                     }
130                 }
131                 for (PartResponse rep :
132                         unchangedModules.getMultiPartResponse().getResponsePartList()) {
133                     if (rep.getKey().equals(SKIPPED_MODULES)) {
134                         unchangedModulesSet.addAll(splitStringFilters(delimiter, rep.getValue()));
135                     } else if (rep.getKey().equals(PRESUBMIT)) {
136                         isPresubmit = Boolean.parseBoolean(rep.getValue());
137                     } else if (rep.getKey().equals(IMAGE_DIGESTS)) {
138                         imageToDigestMap = parseDigests(delimiter, rep.getValue());
139                     } else if (rep.getKey().equals(DELIMITER_NAME)) {
140                         // Ignore
141                     } else {
142                         CLog.w("Unexpected response key '%s' for unchanged modules", rep.getKey());
143                     }
144                 }
145             } else {
146                 CLog.w("Unexpected response for unchanged modules: %s", unchangedModules);
147             }
148         } catch (Exception e) {
149             CLog.e(e);
150         }
151         return new SkipContext(isPresubmit, unchangedModulesSet, imageToDigestMap);
152     }
153 
splitStringFilters(String delimiter, String value)154     private static List<String> splitStringFilters(String delimiter, String value) {
155         if (Strings.isNullOrEmpty(value)) {
156             return new ArrayList<String>();
157         }
158         return Arrays.asList(value.split(delimiter));
159     }
160 
serializeDigest(Map<String, Digest> imageToDigest)161     private static List<String> serializeDigest(Map<String, Digest> imageToDigest) {
162         List<String> serializedItems = new ArrayList<>();
163         for (Entry<String, Digest> entry : imageToDigest.entrySet()) {
164             if (entry.getValue() == null) {
165                 serializedItems.add(String.format("%s=null=null", entry.getKey()));
166             } else {
167                 serializedItems.add(
168                         String.format(
169                                 "%s=%s=%s",
170                                 entry.getKey(),
171                                 entry.getValue().getHash(),
172                                 entry.getValue().getSizeBytes()));
173             }
174         }
175         return serializedItems;
176     }
177 
parseDigests(String delimiter, String serializedString)178     public static Map<String, Digest> parseDigests(String delimiter, String serializedString)
179             throws InvalidProtocolBufferException {
180         Map<String, Digest> imageToDigest = new LinkedHashMap<>();
181         if (Strings.isNullOrEmpty(serializedString)) {
182             return imageToDigest;
183         }
184         for (String sub : serializedString.split(delimiter)) {
185             String[] keyValue = sub.split("=");
186             if ("null".equals(keyValue[1])) {
187                 imageToDigest.put(keyValue[0], null);
188             } else {
189                 imageToDigest.put(
190                         keyValue[0],
191                         Digest.newBuilder()
192                                 .setHash(keyValue[1])
193                                 .setSizeBytes(Long.parseLong(keyValue[2]))
194                                 .build());
195             }
196         }
197         return imageToDigest;
198     }
199 }
200