/* * Copyright (c) 2017 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); you * may not use this file except in compliance with the License. You may * obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. See the License for the specific language governing * permissions and limitations under the License. */ package com.android.vts.servlet; import com.android.vts.entity.CodeCoverageEntity; import com.android.vts.entity.DeviceInfoEntity; import com.android.vts.entity.TestCoverageStatusEntity; import com.android.vts.entity.TestEntity; import com.android.vts.entity.TestRunEntity; import com.android.vts.proto.VtsReportMessage; import com.android.vts.util.FilterUtil; import com.google.cloud.datastore.Datastore; import com.google.cloud.datastore.DatastoreOptions; import com.google.cloud.datastore.PathElement; import com.google.cloud.datastore.StructuredQuery.CompositeFilter; import com.google.cloud.datastore.StructuredQuery.Filter; import com.google.cloud.datastore.StructuredQuery.PropertyFilter; import com.google.gson.Gson; import com.google.visualization.datasource.DataSourceHelper; import com.google.visualization.datasource.DataSourceRequest; import com.google.visualization.datasource.base.DataSourceException; import com.google.visualization.datasource.base.ReasonType; import com.google.visualization.datasource.base.ResponseStatus; import com.google.visualization.datasource.base.StatusType; import com.google.visualization.datasource.base.TypeMismatchException; import com.google.visualization.datasource.datatable.ColumnDescription; import com.google.visualization.datasource.datatable.DataTable; import com.google.visualization.datasource.datatable.TableRow; import com.google.visualization.datasource.datatable.value.DateTimeValue; import com.google.visualization.datasource.datatable.value.NumberValue; import com.google.visualization.datasource.datatable.value.ValueType; import com.googlecode.objectify.Key; import com.ibm.icu.util.GregorianCalendar; import com.ibm.icu.util.TimeZone; import java.io.IOException; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.function.Predicate; import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import static com.googlecode.objectify.ObjectifyService.ofy; /** Represents the servlet that is invoked on loading the coverage overview page. */ public class ShowCoverageOverviewServlet extends BaseServlet { @Override public PageType getNavParentType() { return PageType.COVERAGE_OVERVIEW; } @Override public List getBreadcrumbLinks(HttpServletRequest request) { return null; } @Override public void doGetHandler(HttpServletRequest request, HttpServletResponse response) throws IOException { String pageType = request.getParameter("pageType") == null ? "html" : request.getParameter("pageType"); RequestDispatcher dispatcher; if (pageType.equalsIgnoreCase("html")) { dispatcher = this.getCoverageDispatcher(request, response); try { request.setAttribute("pageType", pageType); response.setStatus(HttpServletResponse.SC_OK); dispatcher.forward(request, response); } catch (ServletException e) { logger.log(Level.SEVERE, "Servlet Exception caught : ", e); } } else { String testName = request.getParameter("testName"); DataTable data = getCoverageDataTable(testName); DataSourceRequest dsRequest = null; try { // Extract the datasource request parameters. dsRequest = new DataSourceRequest(request); // NOTE: If you want to work in restricted mode, which means that only // requests from the same domain can access the data source, uncomment the following // call. // // DataSourceHelper.verifyAccessApproved(dsRequest); // Apply the query to the data table. DataTable newData = DataSourceHelper.applyQuery( dsRequest.getQuery(), data, dsRequest.getUserLocale()); // Set the response. DataSourceHelper.setServletResponse(newData, dsRequest, response); } catch (RuntimeException rte) { logger.log(Level.SEVERE, "A runtime exception has occured", rte); ResponseStatus status = new ResponseStatus( StatusType.ERROR, ReasonType.INTERNAL_ERROR, rte.getMessage()); if (dsRequest == null) { dsRequest = DataSourceRequest.getDefaultDataSourceRequest(request); } DataSourceHelper.setServletErrorResponse(status, dsRequest, response); } catch (DataSourceException e) { if (dsRequest != null) { DataSourceHelper.setServletErrorResponse(e, dsRequest, response); } else { DataSourceHelper.setServletErrorResponse(e, request, response); } } } } private List> getTestRunEntityKeyList( List testCoverageStatusEntityList) { return testCoverageStatusEntityList.stream() .map( testCoverageStatusEntity -> { com.googlecode.objectify.Key testKey = com.googlecode.objectify.Key.create( TestEntity.class, testCoverageStatusEntity.getTestName()); return com.googlecode.objectify.Key.create( testKey, TestRunEntity.class, testCoverageStatusEntity.getUpdatedTimestamp()); }) .collect(Collectors.toList()); } private Predicate isBranchAndDevice(String branch, String device) { return d -> d.getBranch().equals(branch) && d.getBuildFlavor().equals(device); } private Predicate isBranch(String branch) { return d -> d.getBranch().equals(branch); } private Predicate isDevice(String device) { return d -> d.getBuildFlavor().equals(device); } private RequestDispatcher getCoverageDispatcher( HttpServletRequest request, HttpServletResponse response) { String COVERAGE_OVERVIEW_JSP = "WEB-INF/jsp/show_coverage_overview.jsp"; RequestDispatcher dispatcher = null; boolean unfiltered = request.getParameter("unfiltered") != null; boolean showPresubmit = request.getParameter("showPresubmit") != null; boolean showPostsubmit = request.getParameter("showPostsubmit") != null; // If no params are specified, set to default of postsubmit-only. if (!(showPresubmit || showPostsubmit)) { showPostsubmit = true; } // If unfiltered, set showPre- and Post-submit to true for accurate UI. if (unfiltered) { showPostsubmit = true; showPresubmit = true; } // Add test names to list List resultNames = Arrays.stream(VtsReportMessage.TestCaseResult.values()) .map(testCaseResult -> testCaseResult.name()) .collect(Collectors.toList()); Map parameterMap = request.getParameterMap(); List testCoverageStatusEntityList = TestCoverageStatusEntity.getAllTestCoverage(); List> testRunEntityKeyList = new ArrayList<>(); if (Objects.nonNull(parameterMap.get("branch")) || Objects.nonNull(parameterMap.get("device"))) { List> deviceInfoEntityKeyList = TestCoverageStatusEntity.getDeviceInfoEntityKeyList( testCoverageStatusEntityList); Collection deviceInfoEntityMap = ofy().load().keys(() -> deviceInfoEntityKeyList.iterator()).values(); Stream deviceInfoEntityStream = Stream.empty(); if (Objects.nonNull(parameterMap.get("branch")) && Objects.nonNull(parameterMap.get("device"))) { String branch = parameterMap.get("branch")[0]; String device = parameterMap.get("device")[0]; deviceInfoEntityStream = deviceInfoEntityMap.stream().filter(isBranchAndDevice(branch, device)); } else if (Objects.nonNull(parameterMap.get("branch"))) { String branch = parameterMap.get("branch")[0]; deviceInfoEntityStream = deviceInfoEntityMap.stream().filter(isBranch(branch)); } else if (Objects.nonNull(parameterMap.get("device"))) { String device = parameterMap.get("device")[0]; deviceInfoEntityStream = deviceInfoEntityMap.stream().filter(isDevice(device)); } else { logger.log(Level.WARNING, "unmet search condition!"); } testRunEntityKeyList = deviceInfoEntityStream .map( deviceInfoEntity -> { com.googlecode.objectify.Key testKey = com.googlecode.objectify.Key.create( TestEntity.class, deviceInfoEntity .getParent() .getParent() .getName()); return com.googlecode.objectify.Key.create( testKey, TestRunEntity.class, deviceInfoEntity.getParent().getId()); }) .collect(Collectors.toList()); logger.log(Level.INFO, "testRunEntityKeyList size => " + testRunEntityKeyList.size()); } else { testRunEntityKeyList = this.getTestRunEntityKeyList(testCoverageStatusEntityList); } Iterator> testRunEntityKeyIterator = testRunEntityKeyList.iterator(); Map, TestRunEntity> keyTestRunEntityMap = ofy().load().keys(() -> testRunEntityKeyIterator); List> codeCoverageEntityKeyList = new ArrayList<>(); Map testRunEntityMap = new HashMap<>(); for (Map.Entry, TestRunEntity> entry : keyTestRunEntityMap.entrySet()) { com.googlecode.objectify.Key codeCoverageEntityKey = com.googlecode.objectify.Key.create( entry.getKey(), CodeCoverageEntity.class, entry.getValue().getId()); codeCoverageEntityKeyList.add(codeCoverageEntityKey); testRunEntityMap.put(entry.getValue().getId(), entry.getValue()); } Map, CodeCoverageEntity> keyCodeCoverageEntityMap = ofy().load().keys(() -> codeCoverageEntityKeyList.iterator()); Map codeCoverageEntityMap = new HashMap<>(); for (Map.Entry, CodeCoverageEntity> entry : keyCodeCoverageEntityMap.entrySet()) { codeCoverageEntityMap.put(entry.getValue().getId(), entry.getValue()); } int coveredLines = 0; int uncoveredLines = 0; int passCount = 0; int failCount = 0; for (Map.Entry entry : codeCoverageEntityMap.entrySet()) { TestRunEntity testRunEntity = testRunEntityMap.get(entry.getKey()); CodeCoverageEntity codeCoverageEntity = entry.getValue(); coveredLines += codeCoverageEntity.getCoveredLineCount(); uncoveredLines += codeCoverageEntity.getTotalLineCount() - codeCoverageEntity.getCoveredLineCount(); passCount += testRunEntity.getPassCount(); failCount += testRunEntity.getFailCount(); } FilterUtil.setAttributes(request, parameterMap); int[] testStats = new int[VtsReportMessage.TestCaseResult.values().length]; testStats[VtsReportMessage.TestCaseResult.TEST_CASE_RESULT_PASS.getNumber()] = passCount; testStats[VtsReportMessage.TestCaseResult.TEST_CASE_RESULT_FAIL.getNumber()] = failCount; response.setStatus(HttpServletResponse.SC_OK); request.setAttribute("resultNames", resultNames); request.setAttribute("resultNamesJson", new Gson().toJson(resultNames)); request.setAttribute("testRunEntityList", testRunEntityMap.values()); request.setAttribute("codeCoverageEntityMap", codeCoverageEntityMap); request.setAttribute("coveredLines", new Gson().toJson(coveredLines)); request.setAttribute("uncoveredLines", new Gson().toJson(uncoveredLines)); request.setAttribute("testStats", new Gson().toJson(testStats)); request.setAttribute("unfiltered", unfiltered); request.setAttribute("showPresubmit", showPresubmit); request.setAttribute("showPostsubmit", showPostsubmit); request.setAttribute( "deviceOptions", TestCoverageStatusEntity.getDeviceSet(testCoverageStatusEntityList)); request.setAttribute( "branchOptions", TestCoverageStatusEntity.getBranchSet(testCoverageStatusEntityList)); dispatcher = request.getRequestDispatcher(COVERAGE_OVERVIEW_JSP); return dispatcher; } private DataTable getCoverageDataTable(String testName) { Datastore datastore = DatastoreOptions.getDefaultInstance().getService(); DataTable dataTable = new DataTable(); ArrayList cd = new ArrayList<>(); ColumnDescription startDate = new ColumnDescription("startDate", ValueType.DATETIME, "Date"); startDate.setPattern("yyyy-MM-dd"); cd.add(startDate); cd.add( new ColumnDescription( "coveredLineCount", ValueType.NUMBER, "Covered Source Code Line Count")); cd.add( new ColumnDescription( "totalLineCount", ValueType.NUMBER, "Total Source Code Line Count")); cd.add(new ColumnDescription("percentage", ValueType.NUMBER, "Coverage Ratio (%)")); dataTable.addColumns(cd); Calendar cal = Calendar.getInstance(); cal.add(Calendar.MONTH, -6); Long startTime = cal.getTime().getTime() * 1000; Long endTime = Calendar.getInstance().getTime().getTime() * 1000; com.google.cloud.datastore.Key startKey = datastore .newKeyFactory() .setKind(TestRunEntity.KIND) .addAncestors( PathElement.of(TestEntity.KIND, testName), PathElement.of(TestRunEntity.KIND, startTime)) .newKey(startTime); com.google.cloud.datastore.Key endKey = datastore .newKeyFactory() .setKind(TestRunEntity.KIND) .addAncestors( PathElement.of(TestEntity.KIND, testName), PathElement.of(TestRunEntity.KIND, endTime)) .newKey(endTime); Filter codeCoverageFilter = CompositeFilter.and( PropertyFilter.lt("__key__", endKey), PropertyFilter.gt("__key__", startKey)); List codeCoverageEntityList = ofy().load() .type(CodeCoverageEntity.class) .filter(codeCoverageFilter) .limit(10) .list(); DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyy-MM-dd"); Map> codeCoverageEntityListMap = codeCoverageEntityList .stream() .collect( Collectors.groupingBy( v -> dateTimeFormatter.print(v.getId() / 1000))); codeCoverageEntityListMap.forEach( (key, entityList) -> { GregorianCalendar gCal = new GregorianCalendar(); gCal.setTimeZone(TimeZone.getTimeZone("GMT")); gCal.setTimeInMillis(entityList.get(0).getId() / 1000); Long sumCoveredLine = entityList.stream().mapToLong(val -> val.getCoveredLineCount()).sum(); Long sumTotalLine = entityList.stream().mapToLong(val -> val.getTotalLineCount()).sum(); float percentage = 0; if (sumTotalLine > 0) { BigDecimal coveredLineNum = new BigDecimal(sumCoveredLine); BigDecimal totalLineNum = new BigDecimal(sumTotalLine); BigDecimal totalPercent = new BigDecimal(100); percentage = coveredLineNum .multiply(totalPercent) .divide(totalLineNum, 2, RoundingMode.HALF_DOWN) .floatValue(); } TableRow tableRow = new TableRow(); tableRow.addCell(new DateTimeValue(gCal)); tableRow.addCell(new NumberValue(sumCoveredLine)); tableRow.addCell(new NumberValue(sumTotalLine)); tableRow.addCell(new NumberValue(percentage)); try { dataTable.addRow(tableRow); } catch (TypeMismatchException e) { logger.log(Level.WARNING, "Invalid type! "); } }); return dataTable; } }