1 /* 2 * Copyright (c) 2017 Google Inc. All Rights Reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you 5 * may not use this file except in compliance with the License. You may 6 * 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 13 * implied. See the License for the specific language governing 14 * permissions and limitations under the License. 15 */ 16 17 package com.android.vts.entity; 18 19 import static com.googlecode.objectify.ObjectifyService.ofy; 20 21 import com.android.vts.proto.VtsReportMessage.CoverageReportMessage; 22 import com.google.appengine.api.datastore.Entity; 23 import com.google.appengine.api.datastore.Key; 24 import com.googlecode.objectify.annotation.Cache; 25 import com.googlecode.objectify.annotation.Id; 26 import com.googlecode.objectify.annotation.Ignore; 27 import com.googlecode.objectify.annotation.Index; 28 import com.googlecode.objectify.annotation.Parent; 29 import java.io.UnsupportedEncodingException; 30 import java.net.URLEncoder; 31 import java.util.ArrayList; 32 import java.util.Comparator; 33 import java.util.List; 34 import java.util.Objects; 35 import java.util.Properties; 36 import java.util.logging.Level; 37 import java.util.logging.Logger; 38 import lombok.Data; 39 import lombok.Getter; 40 import lombok.NoArgsConstructor; 41 import lombok.Setter; 42 43 @com.googlecode.objectify.annotation.Entity(name = "Coverage") 44 @Cache 45 @Data 46 @NoArgsConstructor 47 /** Object describing coverage data gathered for a file. */ 48 public class CoverageEntity implements DashboardEntity { 49 50 protected static final Logger logger = Logger.getLogger(CoverageEntity.class.getName()); 51 52 public static final String KIND = "Coverage"; 53 54 public static String GERRIT_URI; 55 56 // Property keys 57 public static final String GROUP = "group"; 58 public static final String COVERED_LINE_COUNT = "coveredCount"; 59 public static final String TOTAL_LINE_COUNT = "totalCount"; 60 public static final String FILE_PATH = "filePath"; 61 public static final String PROJECT_NAME = "projectName"; 62 public static final String PROJECT_VERSION = "projectVersion"; 63 public static final String LINE_COVERAGE = "lineCoverage"; 64 65 @Ignore @Getter @Setter private Key parentKey; 66 67 @Id @Getter @Setter private Long id; 68 69 @Parent @Getter @Setter private com.googlecode.objectify.Key<?> testParent; 70 71 @Index @Getter @Setter private String group; 72 73 @Getter @Setter private long coveredCount; 74 75 @Getter @Setter private long totalCount; 76 77 @Index @Getter @Setter private String filePath; 78 79 @Getter @Setter private String projectName; 80 81 @Getter @Setter private String projectVersion; 82 83 @Getter @Setter private List<Long> lineCoverage; 84 85 /** CoverageEntity isIgnored field */ 86 @Index @Getter @Setter Boolean isIgnored; 87 88 /** 89 * Create a CoverageEntity object for a file. 90 * 91 * @param parentKey The key to the parent TestRunEntity object in the database. 92 * @param group The group within the test run describing the coverage. 93 * @param coveredLineCount The total number of covered lines in the file. 94 * @param totalLineCount The total number of uncovered executable lines in the file. 95 * @param filePath The path to the file. 96 * @param projectName The name of the git project. 97 * @param projectVersion The commit hash of the project at the time the test was executed. 98 * @param lineCoverage List of coverage counts per executable line in the file. 99 */ CoverageEntity( Key parentKey, String group, long coveredLineCount, long totalLineCount, String filePath, String projectName, String projectVersion, List<Long> lineCoverage)100 public CoverageEntity( 101 Key parentKey, 102 String group, 103 long coveredLineCount, 104 long totalLineCount, 105 String filePath, 106 String projectName, 107 String projectVersion, 108 List<Long> lineCoverage) { 109 this.parentKey = parentKey; 110 this.group = group; 111 this.coveredCount = coveredLineCount; 112 this.totalCount = totalLineCount; 113 this.filePath = filePath; 114 this.projectName = projectName; 115 this.projectVersion = projectVersion; 116 this.lineCoverage = lineCoverage; 117 } 118 119 /** 120 * Create a CoverageEntity object for a file. 121 * 122 * @param testParent The objectify key to the parent TestRunEntity object in the database. 123 * @param group The group within the test run describing the coverage. 124 * @param coveredLineCount The total number of covered lines in the file. 125 * @param totalLineCount The total number of uncovered executable lines in the file. 126 * @param filePath The path to the file. 127 * @param projectName The name of the git project. 128 * @param projectVersion The commit hash of the project at the time the test was executed. 129 * @param lineCoverage List of coverage counts per executable line in the file. 130 */ CoverageEntity( com.googlecode.objectify.Key testParent, String group, long coveredLineCount, long totalLineCount, String filePath, String projectName, String projectVersion, List<Long> lineCoverage)131 public CoverageEntity( 132 com.googlecode.objectify.Key testParent, 133 String group, 134 long coveredLineCount, 135 long totalLineCount, 136 String filePath, 137 String projectName, 138 String projectVersion, 139 List<Long> lineCoverage) { 140 this.testParent = testParent; 141 this.group = group; 142 this.coveredCount = coveredLineCount; 143 this.totalCount = totalLineCount; 144 this.filePath = filePath; 145 this.projectName = projectName; 146 this.projectVersion = projectVersion; 147 this.lineCoverage = lineCoverage; 148 } 149 150 /** find coverage entity by ID */ findById(String testName, String testRunId, String id)151 public static CoverageEntity findById(String testName, String testRunId, String id) { 152 com.googlecode.objectify.Key testKey = 153 com.googlecode.objectify.Key.create(TestEntity.class, testName); 154 com.googlecode.objectify.Key testRunKey = 155 com.googlecode.objectify.Key.create( 156 testKey, TestRunEntity.class, Long.parseLong(testRunId)); 157 return ofy().load() 158 .type(CoverageEntity.class) 159 .parent(testRunKey) 160 .id(Long.parseLong(id)) 161 .now(); 162 } 163 setPropertyValues(Properties newSystemConfigProp)164 public static void setPropertyValues(Properties newSystemConfigProp) { 165 GERRIT_URI = newSystemConfigProp.getProperty("gerrit.uri"); 166 } 167 168 /** Saving function for the instance of this class */ 169 @Override save()170 public com.googlecode.objectify.Key<CoverageEntity> save() { 171 return ofy().save().entity(this).now(); 172 } 173 174 /** Get percentage from calculating coveredCount and totalCount values */ getPercentage()175 public Double getPercentage() { 176 return Math.round(coveredCount * 10000d / totalCount) / 100d; 177 } 178 179 /** Get Gerrit Url function from the attributes of this class */ getGerritUrl()180 public String getGerritUrl() throws UnsupportedEncodingException { 181 String gerritPath = 182 GERRIT_URI 183 + "/projects/" 184 + URLEncoder.encode(projectName, "UTF-8") 185 + "/commits/" 186 + URLEncoder.encode(projectVersion, "UTF-8") 187 + "/files/" 188 + URLEncoder.encode(filePath, "UTF-8") 189 + "/content"; 190 return gerritPath; 191 } 192 193 /* Comparator for sorting the list by isIgnored field */ 194 public static Comparator<CoverageEntity> isIgnoredComparator = 195 new Comparator<CoverageEntity>() { 196 197 public int compare(CoverageEntity coverageEntity1, CoverageEntity coverageEntity2) { 198 Boolean isIgnored1 = 199 Objects.isNull(coverageEntity1.getIsIgnored()) 200 ? false 201 : coverageEntity1.getIsIgnored(); 202 Boolean isIgnored2 = 203 Objects.isNull(coverageEntity2.getIsIgnored()) 204 ? false 205 : coverageEntity2.getIsIgnored(); 206 207 // ascending order 208 return isIgnored1.compareTo(isIgnored2); 209 } 210 }; 211 toEntity()212 public Entity toEntity() { 213 Entity coverageEntity = new Entity(KIND, parentKey); 214 coverageEntity.setProperty(GROUP, group); 215 coverageEntity.setUnindexedProperty(COVERED_LINE_COUNT, coveredCount); 216 coverageEntity.setUnindexedProperty(TOTAL_LINE_COUNT, totalCount); 217 coverageEntity.setProperty(FILE_PATH, filePath); 218 coverageEntity.setUnindexedProperty(PROJECT_NAME, projectName); 219 coverageEntity.setUnindexedProperty(PROJECT_VERSION, projectVersion); 220 if (lineCoverage != null && lineCoverage.size() > 0) { 221 coverageEntity.setUnindexedProperty(LINE_COVERAGE, lineCoverage); 222 } 223 return coverageEntity; 224 } 225 226 /** 227 * Convert an Entity object to a CoverageEntity. 228 * 229 * @param e The entity to process. 230 * @return CoverageEntity object with the properties from e, or null if incompatible. 231 */ 232 @SuppressWarnings("unchecked") fromEntity(Entity e)233 public static CoverageEntity fromEntity(Entity e) { 234 if (!e.getKind().equals(KIND) 235 || !e.hasProperty(GROUP) 236 || !e.hasProperty(COVERED_LINE_COUNT) 237 || !e.hasProperty(TOTAL_LINE_COUNT) 238 || !e.hasProperty(FILE_PATH) 239 || !e.hasProperty(PROJECT_NAME) 240 || !e.hasProperty(PROJECT_VERSION)) { 241 logger.log(Level.WARNING, "Missing coverage attributes in entity: " + e.toString()); 242 return null; 243 } 244 try { 245 String group = (String) e.getProperty(GROUP); 246 long coveredLineCount = (long) e.getProperty(COVERED_LINE_COUNT); 247 long totalLineCount = (long) e.getProperty(TOTAL_LINE_COUNT); 248 String filePath = (String) e.getProperty(FILE_PATH); 249 String projectName = (String) e.getProperty(PROJECT_NAME); 250 String projectVersion = (String) e.getProperty(PROJECT_VERSION); 251 List<Long> lineCoverage; 252 if (e.hasProperty(LINE_COVERAGE)) { 253 lineCoverage = (List<Long>) e.getProperty(LINE_COVERAGE); 254 } else { 255 lineCoverage = new ArrayList<>(); 256 } 257 return new CoverageEntity( 258 e.getKey().getParent(), 259 group, 260 coveredLineCount, 261 totalLineCount, 262 filePath, 263 projectName, 264 projectVersion, 265 lineCoverage); 266 } catch (ClassCastException exception) { 267 // Invalid contents or null values 268 logger.log(Level.WARNING, "Error parsing coverage entity.", exception); 269 } 270 return null; 271 } 272 273 /** 274 * Convert a coverage report to a CoverageEntity. 275 * 276 * @param parentKey The ancestor key for the coverage entity. 277 * @param group The group to display the coverage report with. 278 * @param coverage The coverage report containing coverage data. 279 * @return The CoverageEntity for the coverage report message, or null if not compatible. 280 */ fromCoverageReport( com.googlecode.objectify.Key parentKey, String group, CoverageReportMessage coverage)281 public static CoverageEntity fromCoverageReport( 282 com.googlecode.objectify.Key parentKey, String group, CoverageReportMessage coverage) { 283 if (!coverage.hasFilePath() 284 || !coverage.hasProjectName() 285 || !coverage.hasRevision() 286 || !coverage.hasTotalLineCount() 287 || !coverage.hasCoveredLineCount()) { 288 return null; // invalid coverage report; 289 } 290 long coveredLineCount = coverage.getCoveredLineCount(); 291 long totalLineCount = coverage.getTotalLineCount(); 292 String filePath = coverage.getFilePath().toStringUtf8(); 293 String projectName = coverage.getProjectName().toStringUtf8(); 294 String projectVersion = coverage.getRevision().toStringUtf8(); 295 List<Long> lineCoverage = null; 296 if (coverage.getLineCoverageVectorCount() > 0) { 297 lineCoverage = new ArrayList<>(); 298 for (long count : coverage.getLineCoverageVectorList()) { 299 lineCoverage.add(count); 300 } 301 } 302 return new CoverageEntity( 303 parentKey, 304 group, 305 coveredLineCount, 306 totalLineCount, 307 filePath, 308 projectName, 309 projectVersion, 310 lineCoverage); 311 } 312 } 313