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