1 /* 2 * Copyright (c) 2018 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.servlet; 18 19 import com.android.vts.entity.DeviceInfoEntity; 20 import com.android.vts.entity.TestPlanEntity; 21 import com.android.vts.entity.TestPlanRunEntity; 22 import com.android.vts.entity.TestSuiteResultEntity; 23 import com.android.vts.util.FilterUtil; 24 import com.google.appengine.api.datastore.DatastoreService; 25 import com.google.appengine.api.datastore.DatastoreServiceFactory; 26 import com.google.appengine.api.datastore.Entity; 27 import com.google.appengine.api.datastore.Key; 28 import com.google.appengine.api.datastore.KeyFactory; 29 import com.google.appengine.api.datastore.PropertyProjection; 30 import com.google.appengine.api.datastore.Query; 31 import com.google.appengine.api.datastore.Query.Filter; 32 import com.google.appengine.api.datastore.Query.SortDirection; 33 34 import javax.servlet.RequestDispatcher; 35 import javax.servlet.ServletException; 36 import javax.servlet.http.HttpServletRequest; 37 import javax.servlet.http.HttpServletResponse; 38 import java.io.IOException; 39 40 import java.util.Arrays; 41 import java.util.ArrayList; 42 import java.util.Calendar; 43 import java.util.Comparator; 44 import java.util.HashMap; 45 import java.util.LinkedList; 46 import java.util.List; 47 import java.util.Map; 48 import java.util.Optional; 49 import java.util.Set; 50 51 import java.util.logging.Level; 52 import java.util.stream.Collectors; 53 54 import static com.googlecode.objectify.ObjectifyService.ofy; 55 56 public class ShowGreenReleaseServlet extends BaseServlet { 57 private static final int MAX_RUNS_PER_PAGE = 9999; 58 59 /** Helper class for displaying each device build info on the green build page. */ 60 public class DeviceBuildInfo implements Comparable<DeviceBuildInfo>, Cloneable { 61 62 /** Device model name ex) marlin, walleye */ 63 private String deviceBuildTarget; 64 /** Candidate Build ID */ 65 private String candidateBuildId; 66 /** Candidate Build ID Timestamp for url parameter */ 67 private Long candidateBuildIdTimestamp; 68 /** Green Build ID */ 69 private String greenBuildId; 70 /** Green Build ID Timestamp for url parameter */ 71 private Long greenBuildIdTimestamp; 72 73 /** 74 * Device Build Info constructor. 75 * 76 * @param deviceBuildTarget The key of the test. 77 * @param candidateBuildId The number of tests failing. 78 */ DeviceBuildInfo(String deviceBuildTarget, String candidateBuildId)79 public DeviceBuildInfo(String deviceBuildTarget, String candidateBuildId) { 80 this.deviceBuildTarget = deviceBuildTarget; 81 this.candidateBuildId = candidateBuildId; 82 this.candidateBuildIdTimestamp = 0L; 83 this.greenBuildId = "N/A"; 84 this.greenBuildIdTimestamp = 0L; 85 } 86 87 /** 88 * Get the device name. 89 * 90 * @return The device name. 91 */ getDeviceBuildTarget()92 public String getDeviceBuildTarget() { 93 return this.deviceBuildTarget; 94 } 95 96 /** 97 * Get the candidate build ID. 98 * 99 * @return The candidate build ID. 100 */ getCandidateBuildId()101 public String getCandidateBuildId() { 102 return this.candidateBuildId; 103 } 104 105 /** Set the candidate build ID. */ setCandidateBuildId(String candidateBuildId)106 public void setCandidateBuildId(String candidateBuildId) { 107 this.candidateBuildId = candidateBuildId; 108 } 109 110 /** 111 * Get the candidate build ID timestamp. 112 * 113 * @return The candidate build ID timestamp. 114 */ getCandidateBuildIdTimestamp()115 public Long getCandidateBuildIdTimestamp() { 116 return this.candidateBuildIdTimestamp; 117 } 118 119 /** Set the candidate build ID timestamp. */ setCandidateBuildIdTimestamp(Long candidateBuildIdTimestamp)120 public void setCandidateBuildIdTimestamp(Long candidateBuildIdTimestamp) { 121 this.candidateBuildIdTimestamp = candidateBuildIdTimestamp; 122 } 123 124 /** 125 * Get the green build ID. 126 * 127 * @return The green build ID. 128 */ getGreenBuildId()129 public String getGreenBuildId() { 130 return this.greenBuildId; 131 } 132 133 /** Set the green build ID. */ setGreenBuildId(String greenBuildId)134 public void setGreenBuildId(String greenBuildId) { 135 this.greenBuildId = greenBuildId; 136 } 137 138 /** 139 * Get the candidate build ID timestamp. 140 * 141 * @return The candidate build ID timestamp. 142 */ getGreenBuildIdTimestamp()143 public Long getGreenBuildIdTimestamp() { 144 return this.greenBuildIdTimestamp; 145 } 146 147 /** Set the candidate build ID timestamp. */ setGreenBuildIdTimestamp(Long greenBuildIdTimestamp)148 public void setGreenBuildIdTimestamp(Long greenBuildIdTimestamp) { 149 this.greenBuildIdTimestamp = greenBuildIdTimestamp; 150 } 151 152 @Override compareTo(DeviceBuildInfo deviceBuildInfo)153 public int compareTo(DeviceBuildInfo deviceBuildInfo) { 154 return this.deviceBuildTarget.compareTo(deviceBuildInfo.getDeviceBuildTarget()); 155 } 156 157 @Override clone()158 protected Object clone() throws CloneNotSupportedException { 159 return super.clone(); 160 } 161 } 162 163 @Override getNavParentType()164 public PageType getNavParentType() { 165 return PageType.RELEASE; 166 } 167 168 @Override getBreadcrumbLinks(HttpServletRequest request)169 public List<Page> getBreadcrumbLinks(HttpServletRequest request) { 170 List<Page> links = new ArrayList<>(); 171 String planName = request.getParameter("plan"); 172 links.add(new Page(PageType.PLAN_RELEASE, planName, "?plan=" + planName)); 173 return links; 174 } 175 176 // This function will build and return basic parameter HashMap based on parameter information 177 // and 178 // this value will also be used on green build page to show the basic structure. getBasicParamMap(Map<String, List<String>> param)179 private Map<String, List<DeviceBuildInfo>> getBasicParamMap(Map<String, List<String>> param) { 180 Map<String, List<DeviceBuildInfo>> basicParamMap = new HashMap<>(); 181 param.forEach( 182 (branch, buildTargetList) -> { 183 List<DeviceBuildInfo> deviceBuildTargetList = new ArrayList<>(); 184 buildTargetList.forEach( 185 buildTargetName -> { 186 deviceBuildTargetList.add( 187 new DeviceBuildInfo(buildTargetName, "N/A")); 188 }); 189 basicParamMap.put(branch, deviceBuildTargetList); 190 }); 191 return basicParamMap; 192 } 193 194 @Override doGetHandler(HttpServletRequest request, HttpServletResponse response)195 public void doGetHandler(HttpServletRequest request, HttpServletResponse response) 196 throws IOException { 197 198 String testType = 199 request.getParameter("type") == null ? "plan" : request.getParameter("type"); 200 201 RequestDispatcher dispatcher; 202 if (testType.equalsIgnoreCase("plan")) { 203 dispatcher = this.getTestPlanDispatcher(request, response); 204 } else { 205 dispatcher = this.getTestSuiteDispatcher(request, response); 206 } 207 208 try { 209 request.setAttribute("testType", testType); 210 response.setStatus(HttpServletResponse.SC_OK); 211 dispatcher.forward(request, response); 212 } catch (ServletException e) { 213 logger.log(Level.SEVERE, "Servlet Exception caught : ", e); 214 } 215 } 216 getTestPlanDispatcher( HttpServletRequest request, HttpServletResponse response)217 private RequestDispatcher getTestPlanDispatcher( 218 HttpServletRequest request, HttpServletResponse response) { 219 String GREEN_RELEASE_JSP = "WEB-INF/jsp/show_green_plan_release.jsp"; 220 221 String testPlan = request.getParameter("plan"); 222 223 Calendar cal = Calendar.getInstance(); 224 cal.add(Calendar.DATE, -7); 225 Long startTime = cal.getTime().getTime() * 1000; 226 Long endTime = Calendar.getInstance().getTime().getTime() * 1000; 227 228 DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); 229 230 Query deviceInfoQuery = 231 new Query(DeviceInfoEntity.KIND) 232 .setAncestor(KeyFactory.createKey(TestPlanEntity.KIND, testPlan)) 233 .addProjection( 234 new PropertyProjection(DeviceInfoEntity.BRANCH, String.class)) 235 .addProjection( 236 new PropertyProjection(DeviceInfoEntity.BUILD_FLAVOR, String.class)) 237 .setDistinct(true); 238 239 Map<String, List<String>> paramInfoMap = new HashMap<>(); 240 for (Entity entity : datastore.prepare(deviceInfoQuery).asIterable()) { 241 String branch = entity.getProperty(DeviceInfoEntity.BRANCH).toString(); 242 String target = entity.getProperty(DeviceInfoEntity.BUILD_FLAVOR).toString(); 243 if (paramInfoMap.containsKey(branch)) { 244 paramInfoMap.get(branch).add(target); 245 } else { 246 paramInfoMap.put(branch, new LinkedList<>(Arrays.asList(target))); 247 } 248 } 249 250 Map<String, List<DeviceBuildInfo>> baseParamMap = getBasicParamMap(paramInfoMap); 251 baseParamMap.forEach( 252 (branchKey, deviceBuildInfoList) -> { 253 List<List<String>> allPassIdLists = new ArrayList<>(); 254 Map<String, List<TestPlanRunEntity>> allTestPlanRunEntityMap = new HashMap<>(); 255 deviceBuildInfoList.forEach( 256 deviceBuildInfo -> { 257 Map<String, String[]> paramMap = 258 new HashMap<String, String[]>() { 259 { 260 put("branch", new String[] {branchKey}); 261 put( 262 "device", 263 new String[] { 264 deviceBuildInfo.getDeviceBuildTarget() 265 }); 266 } 267 }; 268 269 SortDirection dir = SortDirection.DESCENDING; 270 271 boolean unfiltered = false; 272 boolean showPresubmit = false; 273 boolean showPostsubmit = true; 274 Filter typeFilter = 275 FilterUtil.getTestTypeFilter( 276 showPresubmit, showPostsubmit, unfiltered); 277 Key testPlanKey = 278 KeyFactory.createKey(TestPlanEntity.KIND, testPlan); 279 Filter testPlanRunFilter = 280 FilterUtil.getTimeFilter( 281 testPlanKey, 282 TestPlanRunEntity.KIND, 283 startTime, 284 endTime, 285 typeFilter); 286 287 List<Filter> userTestFilters = 288 FilterUtil.getUserTestFilters(paramMap); 289 userTestFilters.add(0, testPlanRunFilter); 290 Filter userDeviceFilter = FilterUtil.getUserDeviceFilter(paramMap); 291 292 List<Key> matchingKeyList = 293 FilterUtil.getMatchingKeys( 294 testPlanKey, 295 TestPlanRunEntity.KIND, 296 userTestFilters, 297 userDeviceFilter, 298 dir, 299 MAX_RUNS_PER_PAGE); 300 301 logger.log( 302 Level.INFO, 303 "the number of matching key => " + matchingKeyList.size()); 304 if (matchingKeyList.size() > 0) { 305 Map<Key, Entity> entityMap = datastore.get(matchingKeyList); 306 307 List<TestPlanRunEntity> testPlanRunEntityList = 308 entityMap 309 .values() 310 .stream() 311 .map( 312 entity -> 313 TestPlanRunEntity.fromEntity( 314 entity)) 315 .collect(Collectors.toList()); 316 317 allTestPlanRunEntityMap.put( 318 branchKey 319 + "-" 320 + deviceBuildInfo.getDeviceBuildTarget(), 321 testPlanRunEntityList); 322 323 // The passBuildIdList containing all passed buildId List for 324 // device 325 List<String> passBuildIdList = 326 testPlanRunEntityList 327 .stream() 328 .filter(entity -> entity.getFailCount() == 0L) 329 .map(entity -> entity.getTestBuildId()) 330 .collect(Collectors.toList()); 331 allPassIdLists.add(passBuildIdList); 332 logger.log(Level.INFO, "passBuildIdList => " + passBuildIdList); 333 334 // The logic for candidate build ID is starting from here 335 Comparator<TestPlanRunEntity> byPassing = 336 Comparator.comparingLong( 337 elemFirst -> elemFirst.getPassCount()); 338 339 Comparator<TestPlanRunEntity> byNonPassing = 340 Comparator.comparingLong( 341 elemFirst -> elemFirst.getFailCount()); 342 343 // This will get the TestPlanRunEntity having maximum number of 344 // passing and minimum number of fail 345 Optional<TestPlanRunEntity> testPlanRunEntity = 346 testPlanRunEntityList 347 .stream() 348 .sorted( 349 byPassing 350 .reversed() 351 .thenComparing(byNonPassing)) 352 .findFirst(); 353 354 String buildId = 355 testPlanRunEntity 356 .map(entity -> entity.getTestBuildId()) 357 .orElse(""); 358 deviceBuildInfo.setCandidateBuildId(buildId); 359 Long buildIdTimestamp = 360 testPlanRunEntity 361 .map( 362 entity -> { 363 return entity.getStartTimestamp(); 364 }) 365 .orElse(0L); 366 deviceBuildInfo.setCandidateBuildIdTimestamp(buildIdTimestamp); 367 } else { 368 allPassIdLists.add(new ArrayList<>()); 369 deviceBuildInfo.setCandidateBuildId("No Test Results"); 370 } 371 }); 372 Set<String> greenBuildIdList = FilterUtil.getCommonElements(allPassIdLists); 373 if (greenBuildIdList.size() > 0) { 374 String greenBuildId = greenBuildIdList.iterator().next(); 375 deviceBuildInfoList.forEach( 376 deviceBuildInfo -> { 377 // This is to get the timestamp for greenBuildId 378 Optional<TestPlanRunEntity> testPlanRunEntity = 379 allTestPlanRunEntityMap 380 .get( 381 branchKey 382 + "-" 383 + deviceBuildInfo 384 .getDeviceBuildTarget()) 385 .stream() 386 .filter( 387 entity -> 388 entity.getFailCount() == 0L 389 && entity.getTestBuildId() 390 .equalsIgnoreCase( 391 greenBuildId)) 392 .findFirst(); 393 // Setting the greenBuildId value and timestamp to 394 // deviceBuildInfo object 395 deviceBuildInfo.setGreenBuildId(greenBuildId); 396 Long buildIdTimestamp = 397 testPlanRunEntity 398 .map(entity -> entity.getStartTimestamp()) 399 .orElse(0L); 400 deviceBuildInfo.setGreenBuildIdTimestamp(buildIdTimestamp); 401 }); 402 } 403 }); 404 405 request.setAttribute("plan", request.getParameter("plan")); 406 request.setAttribute("greenBuildInfo", baseParamMap); 407 RequestDispatcher dispatcher = request.getRequestDispatcher(GREEN_RELEASE_JSP); 408 return dispatcher; 409 } 410 getTestSuiteDispatcher( HttpServletRequest request, HttpServletResponse response)411 private RequestDispatcher getTestSuiteDispatcher( 412 HttpServletRequest request, HttpServletResponse response) { 413 String GREEN_RELEASE_JSP = "WEB-INF/jsp/show_green_suite_release.jsp"; 414 415 String testPlan = request.getParameter("plan"); 416 417 List<TestSuiteResultEntity> branchTargetInfoList = 418 ofy().load() 419 .type(TestSuiteResultEntity.class) 420 .filter("suitePlan", testPlan) 421 .project("branch") 422 .distinct(true) 423 .project("target") 424 .distinct(true) 425 .list(); 426 427 Map<String, List<String>> paramInfoMap = new HashMap<>(); 428 for (TestSuiteResultEntity testSuiteResultEntity : branchTargetInfoList) { 429 String branch = testSuiteResultEntity.getBranch(); 430 String target = testSuiteResultEntity.getTarget(); 431 if (paramInfoMap.containsKey(branch)) { 432 paramInfoMap.get(branch).add(target); 433 } else { 434 paramInfoMap.put(branch, new LinkedList<>(Arrays.asList(target))); 435 } 436 } 437 438 Calendar cal = Calendar.getInstance(); 439 cal.add(Calendar.DATE, -7); 440 Long oneWeekAgoTimestamp = cal.getTime().getTime() * 1000; 441 442 Map<String, List<DeviceBuildInfo>> baseParamMap = getBasicParamMap(paramInfoMap); 443 baseParamMap.forEach( 444 (branchKey, deviceBuildInfoList) -> { 445 List<List<String>> allPassIdLists = new ArrayList<>(); 446 447 deviceBuildInfoList.forEach( 448 deviceBuildInfo -> { 449 List<String> passBuildIdList = 450 ofy().load() 451 .type(TestSuiteResultEntity.class) 452 .filter("suitePlan", testPlan) 453 .filter("branch", branchKey) 454 .filter( 455 "target", 456 deviceBuildInfo.getDeviceBuildTarget()) 457 .filter("failedTestCaseCount", 0) 458 .filterKey( 459 ">=", 460 com.googlecode.objectify.Key.create( 461 TestSuiteResultEntity.class, 462 oneWeekAgoTimestamp)) 463 .project("buildId") 464 .list() 465 .stream() 466 .map(entity -> entity.getBuildId()) 467 .collect(Collectors.toList()); 468 allPassIdLists.add(passBuildIdList); 469 470 TestSuiteResultEntity candidateIdEntity = 471 ofy().load() 472 .type(TestSuiteResultEntity.class) 473 .filter("suitePlan", testPlan) 474 .filter("branch", branchKey) 475 .filter( 476 "target", 477 deviceBuildInfo.getDeviceBuildTarget()) 478 .filterKey( 479 ">=", 480 com.googlecode.objectify.Key.create( 481 TestSuiteResultEntity.class, 482 oneWeekAgoTimestamp)) 483 .project("buildId") 484 .order("__key__") 485 .order("-passedTestCaseRatio") 486 .first() 487 .now(); 488 if (candidateIdEntity == null) { 489 deviceBuildInfo.setCandidateBuildId("N/A"); 490 } else { 491 deviceBuildInfo.setCandidateBuildId( 492 candidateIdEntity.getBuildId()); 493 } 494 }); 495 }); 496 497 request.setAttribute("plan", request.getParameter("plan")); 498 request.setAttribute("greenBuildInfo", baseParamMap); 499 RequestDispatcher dispatcher = request.getRequestDispatcher(GREEN_RELEASE_JSP); 500 return dispatcher; 501 } 502 } 503