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