1 /* 2 * Copyright (C) 2018 The Android Open Source Project 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 com.google.common.base.Strings; 20 import com.googlecode.objectify.Key; 21 import com.googlecode.objectify.annotation.Cache; 22 import com.googlecode.objectify.annotation.Entity; 23 import com.googlecode.objectify.annotation.Id; 24 import com.googlecode.objectify.annotation.Ignore; 25 import com.googlecode.objectify.annotation.Index; 26 import com.googlecode.objectify.annotation.Parent; 27 import java.util.logging.Level; 28 import java.util.logging.Logger; 29 import lombok.EqualsAndHashCode; 30 import lombok.Getter; 31 import lombok.NoArgsConstructor; 32 import lombok.Setter; 33 import org.apache.commons.io.IOUtils; 34 import org.apache.commons.lang.text.StrSubstitutor; 35 import org.apache.http.NameValuePair; 36 import org.apache.http.ParseException; 37 import org.apache.http.client.utils.URIUtils; 38 import org.apache.http.client.utils.URLEncodedUtils; 39 import org.apache.http.message.BasicNameValuePair; 40 41 import java.io.FileNotFoundException; 42 import java.io.IOException; 43 import java.io.InputStream; 44 import java.math.BigDecimal; 45 import java.net.URI; 46 import java.net.URISyntaxException; 47 import java.nio.charset.StandardCharsets; 48 import java.util.ArrayList; 49 import java.util.Arrays; 50 import java.util.Date; 51 import java.util.HashMap; 52 import java.util.List; 53 import java.util.Map; 54 import java.util.Properties; 55 import java.util.regex.Matcher; 56 import java.util.regex.Pattern; 57 import java.util.stream.Collectors; 58 import java.util.stream.IntStream; 59 import java.util.stream.Stream; 60 61 import static com.googlecode.objectify.ObjectifyService.ofy; 62 63 /** Embeded TestType Class for determining testType and search function */ 64 @Index 65 @NoArgsConstructor 66 class TestTypeIndex { 67 68 /** Embeded TOT field, search field name "testTypeIndex.TOT" */ 69 private Boolean TOT; 70 71 /** Embeded OTA field, search field name "testTypeIndex.OTA" */ 72 private Boolean OTA; 73 74 /** Embeded SIGNED field, search field name "testTypeIndex.SIGNED" */ 75 private Boolean SIGNED; 76 77 /** Maximum bit size */ 78 @Ignore private int bitSize = 6; 79 80 @Ignore 81 private List<Integer> totTypeList = this.getTypeList(TestSuiteResultEntity.TestType.TOT.value); 82 83 @Ignore 84 private List<Integer> otaTypeList = this.getTypeList(TestSuiteResultEntity.TestType.OTA.value); 85 86 @Ignore 87 private List<Integer> signedTypeList = 88 this.getTypeList(TestSuiteResultEntity.TestType.SIGNED.value); 89 90 /** Retrieving the list of integers for each category type */ getTypeList(int typeNum)91 private List<Integer> getTypeList(int typeNum) { 92 return IntStream.range(0, (1 << (bitSize - 1))) 93 .filter(i -> (i & typeNum) > 0) 94 .boxed() 95 .collect(Collectors.toList()); 96 } 97 TestTypeIndex(int testType)98 public TestTypeIndex(int testType) { 99 if (totTypeList.contains(testType)) { 100 this.TOT = true; 101 } else { 102 this.TOT = false; 103 } 104 105 if (otaTypeList.contains(testType)) { 106 this.OTA = true; 107 } else { 108 this.OTA = false; 109 } 110 111 if (signedTypeList.contains(testType)) { 112 this.SIGNED = true; 113 } else { 114 this.SIGNED = false; 115 } 116 } 117 } 118 119 /** Entity Class for saving Test Log Summary */ 120 @Cache 121 @Entity 122 @EqualsAndHashCode(of = "id") 123 @NoArgsConstructor 124 public class TestSuiteResultEntity implements DashboardEntity { 125 126 private static final Logger logger = Logger.getLogger(TestSuiteResultEntity.class.getName()); 127 128 /** Bug Tracking System Property class */ 129 private static Properties bugTrackingSystemProp = new Properties(); 130 131 /** System Configuration Property class */ 132 private static Properties systemConfigProp = new Properties(); 133 134 public enum TestType { 135 UNKNOWN(0), 136 TOT(1), 137 OTA(1 << 1), 138 SIGNED(1 << 2), 139 PRESUBMIT(1 << 3), 140 MANUAL(1 << 5); 141 142 public int value; 143 TestType(int value)144 TestType(int value) { 145 this.value = value; 146 } 147 value()148 public int value() { 149 return value; 150 } 151 } 152 153 @Parent @Getter Key<TestSuiteFileEntity> testSuiteFileEntityKey; 154 155 /** Test Suite start time field */ 156 @Id @Getter @Setter Long startTime; 157 158 /** Test Suite end time field */ 159 @Getter @Setter Long endTime; 160 161 /** Test Suite test type field */ 162 @Getter @Setter int testType; 163 164 /** Embeded test type field */ 165 @Index @Getter @Setter TestTypeIndex testTypeIndex; 166 167 /** Test Suite bootup error field */ 168 @Getter @Setter Boolean bootSuccess; 169 170 /** Test Suite result path field */ 171 @Getter @Setter String resultPath; 172 173 /** Test Suite infra log path field */ 174 @Getter @Setter String infraLogPath; 175 176 /** Test Suite device name field */ 177 @Index @Getter @Setter String deviceName; 178 179 /** Test Suite host name field */ 180 @Index @Getter @Setter String hostName; 181 182 /** Test Suite plan field */ 183 @Index @Getter @Setter String suitePlan; 184 185 /** Test Suite version field */ 186 @Getter @Setter String suiteVersion; 187 188 /** Test Suite name field */ 189 @Getter @Setter String suiteName; 190 191 /** Test Suite build number field */ 192 @Getter @Setter String suiteBuildNumber; 193 194 /** Test Suite test finished module count field */ 195 @Getter @Setter int modulesDone; 196 197 /** Test Suite total number of module field */ 198 @Getter @Setter int modulesTotal; 199 200 /** Test Suite branch field */ 201 @Index @Getter @Setter String branch; 202 203 /** Test Suite build target field */ 204 @Index @Getter @Setter String target; 205 206 /** Test Suite build ID field */ 207 @Index @Getter @Setter String buildId; 208 209 /** Test Suite system fingerprint field */ 210 @Getter @Setter String buildSystemFingerprint; 211 212 /** Test Suite vendor fingerprint field */ 213 @Getter @Setter String buildVendorFingerprint; 214 215 /** Test Suite test count for success field */ 216 @Index @Getter @Setter int passedTestCaseCount; 217 218 /** Test Suite test count for failure field */ 219 @Index @Getter @Setter int failedTestCaseCount; 220 221 /** Test Suite ratio of success to find candidate build */ 222 @Index @Getter @Setter float passedTestCaseRatio; 223 224 /** When this record was created or updated */ 225 @Index @Getter Date updated; 226 227 /** Construction function for TestSuiteResultEntity Class */ TestSuiteResultEntity( Key<TestSuiteFileEntity> testSuiteFileEntityKey, Long startTime, Long endTime, int testType, Boolean bootSuccess, String resultPath, String infraLogPath, String hostName, String suitePlan, String suiteVersion, String suiteName, String suiteBuildNumber, int modulesDone, int modulesTotal, String branch, String target, String buildId, String buildSystemFingerprint, String buildVendorFingerprint, int passedTestCaseCount, int failedTestCaseCount)228 public TestSuiteResultEntity( 229 Key<TestSuiteFileEntity> testSuiteFileEntityKey, 230 Long startTime, 231 Long endTime, 232 int testType, 233 Boolean bootSuccess, 234 String resultPath, 235 String infraLogPath, 236 String hostName, 237 String suitePlan, 238 String suiteVersion, 239 String suiteName, 240 String suiteBuildNumber, 241 int modulesDone, 242 int modulesTotal, 243 String branch, 244 String target, 245 String buildId, 246 String buildSystemFingerprint, 247 String buildVendorFingerprint, 248 int passedTestCaseCount, 249 int failedTestCaseCount) { 250 this.testSuiteFileEntityKey = testSuiteFileEntityKey; 251 this.startTime = startTime; 252 this.endTime = endTime; 253 this.bootSuccess = bootSuccess; 254 this.resultPath = resultPath; 255 this.infraLogPath = infraLogPath; 256 this.hostName = hostName; 257 this.suitePlan = suitePlan; 258 this.suiteVersion = suiteVersion; 259 this.suiteName = suiteName; 260 this.suiteBuildNumber = suiteBuildNumber; 261 this.modulesDone = modulesDone; 262 this.modulesTotal = modulesTotal; 263 this.branch = branch; 264 this.target = target; 265 this.buildId = buildId; 266 this.buildSystemFingerprint = buildSystemFingerprint; 267 this.buildVendorFingerprint = buildVendorFingerprint; 268 this.passedTestCaseCount = passedTestCaseCount; 269 this.failedTestCaseCount = failedTestCaseCount; 270 271 BigDecimal totalTestCaseCount = new BigDecimal(passedTestCaseCount + failedTestCaseCount); 272 if (totalTestCaseCount.intValue() <= 0) { 273 this.passedTestCaseRatio = 0; 274 } else { 275 BigDecimal passedTestCaseCountDecimal = new BigDecimal(passedTestCaseCount); 276 BigDecimal result = 277 passedTestCaseCountDecimal.divide( 278 totalTestCaseCount, 10, BigDecimal.ROUND_FLOOR); 279 this.passedTestCaseRatio = result.floatValue() * 100; 280 } 281 282 if (!this.buildVendorFingerprint.isEmpty()) { 283 this.deviceName = this.getDeviceNameFromVendorFpt(); 284 } 285 286 this.testType = this.getSuiteResultTestType(testType); 287 this.testTypeIndex = new TestTypeIndex(this.testType); 288 } 289 290 /** Saving function for the instance of this class */ save(TestSuiteFileEntity newTestSuiteFileEntity)291 public void save(TestSuiteFileEntity newTestSuiteFileEntity) { 292 List<String> checkList = 293 Arrays.asList( 294 this.hostName, 295 this.suitePlan, 296 this.suiteName, 297 this.suiteBuildNumber, 298 this.branch, 299 this.target, 300 this.buildId); 301 boolean isAllTrue = checkList.stream().allMatch(val -> Strings.isNullOrEmpty(val)); 302 303 if (isAllTrue) { 304 logger.log(Level.WARNING, "There is null or empty string among required fields!"); 305 } else { 306 this.updated = new Date(); 307 ofy().transact(() -> { 308 newTestSuiteFileEntity.save(); 309 ofy().save().entity(this).now(); 310 }); 311 } 312 } 313 314 /** Saving function for the instance of this class */ 315 @Override save()316 public Key<TestSuiteResultEntity> save() { 317 return ofy().save().entity(this).now(); 318 } 319 setPropertyValues(Properties newSystemConfigProp)320 public static void setPropertyValues(Properties newSystemConfigProp) { 321 systemConfigProp = newSystemConfigProp; 322 bugTrackingSystemProp = getBugTrackingSystemProp(newSystemConfigProp); 323 } 324 getBugTrackingSystemProp(Properties newSystemConfigProp)325 private static Properties getBugTrackingSystemProp(Properties newSystemConfigProp) { 326 Properties newBugTrackingSystemProp = new Properties(); 327 try { 328 String bugTrackingSystem = newSystemConfigProp.getProperty("bug.tracking.system"); 329 330 if (!bugTrackingSystem.isEmpty()) { 331 InputStream btsInputStream = 332 TestSuiteResultEntity.class 333 .getClassLoader() 334 .getResourceAsStream( 335 "bug_tracking_system/" 336 + bugTrackingSystem 337 + "/config.properties"); 338 newBugTrackingSystemProp.load(btsInputStream); 339 } 340 } catch (FileNotFoundException e) { 341 e.printStackTrace(); 342 } catch (IOException e) { 343 e.printStackTrace(); 344 } finally { 345 return newBugTrackingSystemProp; 346 } 347 } 348 getTestSuitePlans()349 public static List<TestSuiteResultEntity> getTestSuitePlans() { 350 return ofy().load() 351 .type(TestSuiteResultEntity.class) 352 .project("suitePlan") 353 .distinct(true) 354 .list(); 355 } 356 getBranchDistinctList()357 public static List<TestSuiteResultEntity> getBranchDistinctList() { 358 return ofy().load() 359 .type(TestSuiteResultEntity.class) 360 .project("branch") 361 .distinct(true) 362 .list(); 363 } 364 getBuildIdDistinctList()365 public static List<TestSuiteResultEntity> getBuildIdDistinctList() { 366 return ofy().load() 367 .type(TestSuiteResultEntity.class) 368 .project("buildId") 369 .distinct(true) 370 .list(); 371 } 372 getTargetDistinctList()373 public static List<TestSuiteResultEntity> getTargetDistinctList() { 374 return ofy().load() 375 .type(TestSuiteResultEntity.class) 376 .project("target") 377 .distinct(true) 378 .list(); 379 } 380 getHostNameDistinctList()381 public static List<TestSuiteResultEntity> getHostNameDistinctList() { 382 return ofy().load() 383 .type(TestSuiteResultEntity.class) 384 .project("hostName") 385 .distinct(true) 386 .list(); 387 } 388 getDeviceNameFromVendorFpt()389 public String getDeviceNameFromVendorFpt() { 390 String deviceName = 391 Stream.of(this.buildVendorFingerprint.split("/")).skip(1).findFirst().orElse(""); 392 return deviceName; 393 } 394 getScreenResultLogPath()395 public String getScreenResultLogPath() { 396 String gcsBucketName = systemConfigProp.getProperty("gcs.bucketName"); 397 return resultPath.replace("gs://" + gcsBucketName + "/", ""); 398 } 399 getScreenInfraLogPath()400 public String getScreenInfraLogPath() { 401 String gcsInfraLogBucketName = systemConfigProp.getProperty("gcs.infraLogBucketName"); 402 return infraLogPath.replace("gs://" + gcsInfraLogBucketName + "/", ""); 403 } 404 getNormalizedVersion(String fingerprint)405 private String getNormalizedVersion(String fingerprint) { 406 Map<String, Pattern> partternMap = 407 new HashMap<String, Pattern>() { 408 { 409 put( 410 "9", 411 Pattern.compile( 412 "(:9(\\.\\d\\.\\d|\\.\\d|)|:P\\w*/)", 413 Pattern.CASE_INSENSITIVE)); 414 put( 415 "8.1", 416 Pattern.compile( 417 "(:8\\.1\\.\\d\\/|:O\\w+-MR1/)", Pattern.CASE_INSENSITIVE)); 418 put( 419 "8", 420 Pattern.compile( 421 "(:8\\.0\\.\\d\\/|:O\\w*/)", Pattern.CASE_INSENSITIVE)); 422 } 423 }; 424 425 for (Map.Entry<String, Pattern> entry : partternMap.entrySet()) { 426 Matcher systemMatcher = entry.getValue().matcher(fingerprint); 427 if (systemMatcher.find()) { 428 return entry.getKey(); 429 } 430 } 431 return "unknown-version"; 432 } 433 getSuiteResultTestType(int testType)434 public int getSuiteResultTestType(int testType) { 435 if (testType == TestType.UNKNOWN.value()) { 436 if (this.getNormalizedVersion(this.buildSystemFingerprint) 437 != this.getNormalizedVersion(this.buildVendorFingerprint)) { 438 return TestType.OTA.value(); 439 } else if (this.buildVendorFingerprint.endsWith("release-keys")) { 440 return TestType.SIGNED.value(); 441 } else { 442 return TestType.TOT.value(); 443 } 444 } else { 445 return testType; 446 } 447 } 448 getLabInfraIssueDescription()449 private String getLabInfraIssueDescription() throws IOException { 450 451 String bugTrackingSystem = systemConfigProp.getProperty("bug.tracking.system"); 452 453 String templateName = 454 bugTrackingSystemProp.getProperty( 455 bugTrackingSystem + ".labInfraIssue.template.name"); 456 457 InputStream inputStream = 458 this.getClass() 459 .getClassLoader() 460 .getResourceAsStream( 461 "bug_tracking_system/" + bugTrackingSystem + "/" + templateName); 462 463 String templateDescription = IOUtils.toString(inputStream, StandardCharsets.UTF_8.name()); 464 465 Map<String, String> valuesMap = new HashMap<>(); 466 valuesMap.put("suiteBuildNumber", suiteBuildNumber); 467 valuesMap.put("buildId", buildId); 468 valuesMap.put("modulesDone", Integer.toString(modulesDone)); 469 valuesMap.put("modulesTotal", Integer.toString(modulesTotal)); 470 valuesMap.put("hostName", hostName); 471 valuesMap.put("resultPath", resultPath); 472 valuesMap.put("buildVendorFingerprint", buildVendorFingerprint); 473 valuesMap.put("buildSystemFingerprint", buildSystemFingerprint); 474 475 StrSubstitutor sub = new StrSubstitutor(valuesMap); 476 String resolvedDescription = sub.replace(templateDescription); 477 478 return resolvedDescription; 479 } 480 getCrashSecurityDescription()481 private String getCrashSecurityDescription() throws IOException { 482 483 String bugTrackingSystem = systemConfigProp.getProperty("bug.tracking.system"); 484 485 String templateName = 486 bugTrackingSystemProp.getProperty( 487 bugTrackingSystem + ".crashSecurity.template.name"); 488 489 InputStream inputStream = 490 this.getClass() 491 .getClassLoader() 492 .getResourceAsStream( 493 "bug_tracking_system/" + bugTrackingSystem + "/" + templateName); 494 495 String templateDescription = IOUtils.toString(inputStream, StandardCharsets.UTF_8.name()); 496 497 Map<String, String> valuesMap = new HashMap<>(); 498 valuesMap.put("suiteBuildNumber", suiteBuildNumber); 499 valuesMap.put("branch", branch); 500 valuesMap.put("target", target); 501 valuesMap.put("deviceName", deviceName); 502 valuesMap.put("buildId", buildId); 503 valuesMap.put("suiteName", suiteName); 504 valuesMap.put("suitePlan", suitePlan); 505 valuesMap.put("hostName", hostName); 506 valuesMap.put("resultPath", resultPath); 507 508 StrSubstitutor sub = new StrSubstitutor(valuesMap); 509 String resolvedDescription = sub.replace(templateDescription); 510 511 return resolvedDescription; 512 } 513 getBuganizerLink()514 public String getBuganizerLink() throws IOException, ParseException, URISyntaxException { 515 516 String bugTrackingSystem = systemConfigProp.getProperty("bug.tracking.system"); 517 518 List<NameValuePair> qparams = new ArrayList<NameValuePair>(); 519 if (!this.bootSuccess || (this.passedTestCaseCount == 0 && this.failedTestCaseCount == 0)) { 520 qparams.add( 521 new BasicNameValuePair( 522 "component", 523 this.bugTrackingSystemProp.getProperty( 524 bugTrackingSystem + ".labInfraIssue.component.id"))); 525 qparams.add( 526 new BasicNameValuePair( 527 "template", 528 this.bugTrackingSystemProp.getProperty( 529 bugTrackingSystem + ".labInfraIssue.template.id"))); 530 qparams.add(new BasicNameValuePair("description", this.getLabInfraIssueDescription())); 531 } else { 532 qparams.add( 533 new BasicNameValuePair( 534 "component", 535 this.bugTrackingSystemProp.getProperty( 536 bugTrackingSystem + ".crashSecurity.component.id"))); 537 qparams.add( 538 new BasicNameValuePair( 539 "template", 540 this.bugTrackingSystemProp.getProperty( 541 bugTrackingSystem + ".crashSecurity.template.id"))); 542 qparams.add(new BasicNameValuePair("description", this.getCrashSecurityDescription())); 543 } 544 545 URI uri = 546 URIUtils.createURI( 547 this.bugTrackingSystemProp.getProperty(bugTrackingSystem + ".uri.scheme"), 548 this.bugTrackingSystemProp.getProperty(bugTrackingSystem + ".uri.host"), 549 -1, 550 this.bugTrackingSystemProp.getProperty(bugTrackingSystem + ".uri.path"), 551 URLEncodedUtils.format(qparams, StandardCharsets.UTF_8.name()), 552 null); 553 return uri.toString(); 554 } 555 } 556