1 /*
2  * Copyright (C) 2023 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 
18 import com.android.tradefed.build.BuildInfoKey.BuildInfoFileKey;
19 import com.android.tradefed.build.IBuildInfo;
20 import com.android.tradefed.build.content.ContentAnalysisContext;
21 import com.android.tradefed.build.content.ContentAnalysisResults;
22 import com.android.tradefed.build.content.ImageContentAnalyzer;
23 import com.android.tradefed.build.content.TestContentAnalyzer;
24 import com.android.tradefed.build.content.ContentAnalysisContext.AnalysisMethod;
25 import com.android.tradefed.device.ITestDevice;
26 import com.android.tradefed.device.NullDevice;
27 import com.android.tradefed.invoker.TestInformation;
28 import com.android.tradefed.invoker.logger.InvocationMetricLogger;
29 import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
30 import com.android.tradefed.invoker.tracing.CloseableTraceScope;
31 import com.android.tradefed.log.LogUtil.CLog;
32 import com.android.tradefed.util.MultiMap;
33 import com.android.tradefed.util.SystemUtil;
34 
35 import java.util.ArrayList;
36 import java.util.List;
37 import java.util.Map.Entry;
38 
39 /** A utility that helps analyze the build artifacts for insight. */
40 public class ArtifactsAnalyzer {
41 
42     // A build attribute describing that the device image didn't change from base build
43     public static final String DEVICE_IMAGE_NOT_CHANGED = "DEVICE_IMAGE_NOT_CHANGED";
44 
45     private final TestInformation information;
46     private final MultiMap<ITestDevice, ContentAnalysisContext> mImageAnalysis;
47     private final List<ContentAnalysisContext> mTestArtifactsAnalysisContent;
48     private final List<String> mModulesDiscovered;
49     private final List<String> mDependencyFiles;
50     private final AnalysisHeuristic mAnalysisLevel;
51 
ArtifactsAnalyzer( TestInformation information, MultiMap<ITestDevice, ContentAnalysisContext> imageAnalysis, List<ContentAnalysisContext> testAnalysisContexts, List<String> moduleDiscovered, List<String> dependencyFiles, AnalysisHeuristic analysisLevel)52     public ArtifactsAnalyzer(
53             TestInformation information,
54             MultiMap<ITestDevice, ContentAnalysisContext> imageAnalysis,
55             List<ContentAnalysisContext> testAnalysisContexts,
56             List<String> moduleDiscovered,
57             List<String> dependencyFiles,
58             AnalysisHeuristic analysisLevel) {
59         this.information = information;
60         this.mImageAnalysis = imageAnalysis;
61         this.mTestArtifactsAnalysisContent = testAnalysisContexts;
62         this.mModulesDiscovered = moduleDiscovered;
63         this.mDependencyFiles = dependencyFiles;
64         this.mAnalysisLevel = analysisLevel;
65     }
66 
analyzeArtifacts()67     public BuildAnalysis analyzeArtifacts() {
68         if (SystemUtil.isLocalMode()) {
69             return null;
70         }
71         List<BuildAnalysis> reports = new ArrayList<>();
72         for (Entry<ITestDevice, IBuildInfo> deviceBuild :
73                 information.getContext().getDeviceBuildMap().entrySet()) {
74             BuildAnalysis report =
75                     analyzeArtifact(deviceBuild, mImageAnalysis.get(deviceBuild.getKey()));
76             reports.add(report);
77         }
78         if (reports.size() > 1) {
79             InvocationMetricLogger.addInvocationMetrics(
80                     InvocationMetricKey.MULTI_DEVICES_CONTENT_ANALYSIS, reports.size());
81         }
82         BuildAnalysis finalReport = BuildAnalysis.mergeReports(reports);
83         CLog.d("Build analysis report: %s", finalReport.toString());
84         boolean presubmit = "WORK_NODE".equals(information.getContext().getAttribute("trigger"));
85         // Do the analysis regardless
86         if (finalReport.hasTestsArtifacts()) {
87             if (mTestArtifactsAnalysisContent.isEmpty()) {
88                 // Couldn't do analysis, assume changes
89                 finalReport.setChangesInTests(true);
90             } else {
91                 try (CloseableTraceScope ignored =
92                         new CloseableTraceScope(
93                                 InvocationMetricKey.TestContentAnalyzer.toString())) {
94                     TestContentAnalyzer analyzer =
95                             new TestContentAnalyzer(
96                                     information,
97                                     presubmit,
98                                     mTestArtifactsAnalysisContent,
99                                     mModulesDiscovered,
100                                     mDependencyFiles);
101                     ContentAnalysisResults analysisResults = analyzer.evaluate();
102                     if (analysisResults == null) {
103                         finalReport.setChangesInTests(true);
104                     } else {
105                         CLog.d("%s", analysisResults.toString());
106                         finalReport.setChangesInTests(analysisResults.hasAnyTestsChange());
107                     }
108                 } catch (RuntimeException e) {
109                     CLog.e(e);
110                     return null;
111                 }
112             }
113         }
114         CLog.d("Analysis report after test analysis: %s", finalReport.toString());
115         return finalReport;
116     }
117 
analyzeArtifact( Entry<ITestDevice, IBuildInfo> deviceBuild, List<ContentAnalysisContext> context)118     private BuildAnalysis analyzeArtifact(
119             Entry<ITestDevice, IBuildInfo> deviceBuild, List<ContentAnalysisContext> context) {
120         ITestDevice device = deviceBuild.getKey();
121         IBuildInfo build = deviceBuild.getValue();
122         boolean deviceImageChanged = true; // anchor toward changing
123         if (device.getIDevice() != null
124                 && device.getIDevice().getClass().isAssignableFrom(NullDevice.class)) {
125             deviceImageChanged = false; // No device image
126             InvocationMetricLogger.addInvocationMetrics(
127                     InvocationMetricKey.DEVICELESS_CONTENT_ANALYSIS, 1);
128         } else {
129             deviceImageChanged =
130                     !"true".equals(build.getBuildAttributes().get(DEVICE_IMAGE_NOT_CHANGED));
131             if (context != null) {
132                 boolean presubmit =
133                         "WORK_NODE".equals(information.getContext().getAttribute("trigger"));
134                 boolean hasOneDeviceAnalysis =
135                         context.stream()
136                                 .anyMatch(
137                                         c ->
138                                                 c.analysisMethod()
139                                                         .equals(AnalysisMethod.DEVICE_IMAGE));
140                 ImageContentAnalyzer analyze =
141                         new ImageContentAnalyzer(presubmit, context, mAnalysisLevel);
142                 ContentAnalysisResults res = analyze.evaluate();
143                 if (res == null) {
144                     deviceImageChanged = true;
145                 } else {
146                     if (hasOneDeviceAnalysis) {
147                         if (res.hasDeviceImageChanges()) {
148                             CLog.d("Changes in device image.");
149                             deviceImageChanged = true;
150                         } else {
151                             deviceImageChanged = false;
152                             InvocationMetricLogger.addInvocationMetrics(
153                                     InvocationMetricKey.DEVICE_IMAGE_NOT_CHANGED, 1);
154                         }
155                     } else if (!deviceImageChanged) {
156                         InvocationMetricLogger.addInvocationMetrics(
157                                 InvocationMetricKey.DEVICE_IMAGE_NOT_CHANGED, 1);
158                     }
159                     if (res.hasAnyBuildKeyChanges()) {
160                         InvocationMetricLogger.addInvocationMetrics(
161                                 InvocationMetricKey.IMAGE_CHANGES_IN_KEY_FILE, 1);
162                         CLog.d("Changes in build key for device image.");
163                         deviceImageChanged = true;
164                     }
165                 }
166             }
167         }
168         boolean hasTestsArtifacts = true;
169         if (build.getFile(BuildInfoFileKey.TESTDIR_IMAGE) == null
170                 && build.getFile(BuildInfoFileKey.ROOT_DIRECTORY) == null) {
171             hasTestsArtifacts = false;
172         }
173         return new BuildAnalysis(deviceImageChanged, hasTestsArtifacts);
174     }
175 }
176