/* * 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.util; import com.android.vts.entity.CodeCoverageEntity; import com.android.vts.entity.DeviceInfoEntity; import com.android.vts.entity.ProfilingPointRunEntity; import com.android.vts.entity.TestCaseRunEntity; import com.android.vts.entity.TestCaseRunEntity.TestCase; import com.android.vts.entity.TestEntity; import com.android.vts.entity.TestRunEntity; import com.android.vts.proto.VtsReportMessage.TestCaseResult; import com.android.vts.util.UrlUtil.LinkDisplay; import com.google.appengine.api.datastore.DatastoreService; import com.google.appengine.api.datastore.DatastoreServiceFactory; import com.google.appengine.api.datastore.Entity; import com.google.appengine.api.datastore.Key; import com.google.appengine.api.datastore.KeyFactory; import com.google.appengine.api.datastore.Query; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.lang.StringUtils; /** Helper object for describing test results data. */ public class TestResults { private final Logger logger = Logger.getLogger(getClass().getName()); private List testRuns; // list of all test runs private Map> testCaseRunMap; // map from test run key to the test run information private Map> deviceInfoMap; // map from test run key to device info private Map testCaseNameMap; // map from test case name to its order private Set profilingPointNameSet; // set of profiling point names public String testName; public String[] headerRow; // row to display above the test results table public String[][] timeGrid; // grid of data storing timestamps to render as dates public String[][] durationGrid; // grid of data storing timestamps to render as time intervals public String[][] summaryGrid; // grid of data displaying a summary of the test run public String[][] resultsGrid; // grid of data displaying test case results public String[] profilingPointNames; // list of profiling point names in the test run public Map> logInfoMap; // map from test run index to url/display pairs public int[] totResultCounts; // array of test result counts for the tip-of-tree runs public String totBuildId = ""; // build ID of tip-of-tree run public long startTime = Long.MAX_VALUE; // oldest timestamp displayed in the results table public long endTime = Long.MIN_VALUE; // newest timestamp displayed in the results table // Row labels for the test time-formatted information. private static final String[] TIME_INFO_NAMES = {"Test Start", "Test End"}; // Row labels for the test duration information. private static final String[] DURATION_INFO_NAMES = {"Test Duration"}; // Row labels for the test summary grid. private static final String[] SUMMARY_NAMES = { "Total", "Passing #", "Non-Passing #", "Passing %", "Covered Lines", "Coverage %", "Links" }; // Row labels for the device summary information in the table header. private static final String[] HEADER_NAMES = { "Stats Type \\ Device Build ID", "Branch", "Build Target", "Device", "ABI Target", "VTS Build ID", "Hostname" }; /** * Create a test results object. * * @param testName The name of the test. */ public TestResults(String testName) { this.testName = testName; this.testRuns = new ArrayList<>(); this.deviceInfoMap = new HashMap<>(); this.testCaseRunMap = new HashMap<>(); this.testCaseNameMap = new HashMap<>(); this.logInfoMap = new HashMap<>(); this.profilingPointNameSet = new HashSet<>(); } /** * Add a test run to the test results. * * @param testRun The Entity containing the test run information. * @param testCaseRuns The collection of test case executions within the test run. */ public void addTestRun(Entity testRun, Iterable testCaseRuns) { TestRunEntity testRunEntity = TestRunEntity.fromEntity(testRun); if (testRunEntity == null) return; if (testRunEntity.getStartTimestamp() < startTime) { startTime = testRunEntity.getStartTimestamp(); } if (testRunEntity.getStartTimestamp() > endTime) { endTime = testRunEntity.getStartTimestamp(); } testRuns.add(testRunEntity); testCaseRunMap.put(testRun.getKey(), new ArrayList()); // Process the test cases in the test run for (Entity e : testCaseRuns) { TestCaseRunEntity testCaseRunEntity = TestCaseRunEntity.fromEntity(e); if (testCaseRunEntity == null) continue; testCaseRunMap.get(testRun.getKey()).add(testCaseRunEntity); for (TestCase testCase : testCaseRunEntity.testCases) { if (!testCaseNameMap.containsKey(testCase.name)) { testCaseNameMap.put(testCase.name, testCaseNameMap.size()); } } } } /** Creates a test case breakdown of the most recent test run. */ private void generateToTBreakdown() { totResultCounts = new int[TestCaseResult.values().length]; if (testRuns.size() == 0) return; TestRunEntity mostRecentRun = testRuns.get(0); List testCaseResults = testCaseRunMap.get(mostRecentRun.getKey()); List deviceInfos = deviceInfoMap.get(mostRecentRun.getKey()); if (deviceInfos.size() > 0) { DeviceInfoEntity totDevice = deviceInfos.get(0); totBuildId = totDevice.getBuildId(); } // Count array for each test result for (TestCaseRunEntity testCaseRunEntity : testCaseResults) { for (TestCase testCase : testCaseRunEntity.testCases) { totResultCounts[testCase.result]++; } } } /** * Get the number of test runs observed. * * @return The number of test runs observed. */ public int getSize() { return testRuns.size(); } /** Fetch and process profiling point names for the set of test runs. */ private void processProfilingPoints() { DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); Key testKey = KeyFactory.createKey(TestEntity.KIND, this.testName); Query.Filter profilingFilter = FilterUtil.getProfilingTimeFilter( testKey, TestRunEntity.KIND, this.startTime, this.endTime); Query profilingPointQuery = new Query(ProfilingPointRunEntity.KIND) .setAncestor(testKey) .setFilter(profilingFilter) .setKeysOnly(); Iterable profilingPoints = datastore.prepare(profilingPointQuery).asIterable(); // Process the profiling point observations in the test run for (Entity e : profilingPoints) { if (e.getKey().getName() != null) { profilingPointNameSet.add(e.getKey().getName()); } } } /** Fetch and process device information for the set of test runs. */ private void processDeviceInfos() { DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); Key testKey = KeyFactory.createKey(TestEntity.KIND, this.testName); Query.Filter deviceFilter = FilterUtil.getDeviceTimeFilter( testKey, TestRunEntity.KIND, this.startTime, this.endTime); Query deviceQuery = new Query(DeviceInfoEntity.KIND) .setAncestor(testKey) .setFilter(deviceFilter) .setKeysOnly(); List deviceGets = new ArrayList<>(); for (Entity device : datastore.prepare(deviceQuery).asIterable(DatastoreHelper.getLargeBatchOptions())) { if (testCaseRunMap.containsKey(device.getParent())) { deviceGets.add(device.getKey()); } } Map devices = datastore.get(deviceGets); for (Key key : devices.keySet()) { Entity device = devices.get(key); if (!testCaseRunMap.containsKey(device.getParent())) return; DeviceInfoEntity deviceEntity = DeviceInfoEntity.fromEntity(device); if (deviceEntity == null) return; if (!deviceInfoMap.containsKey(device.getParent())) { deviceInfoMap.put(device.getParent(), new ArrayList()); } deviceInfoMap.get(device.getParent()).add(deviceEntity); } } /** Post-process the test runs to generate reports of the results. */ public void processReport() { if (getSize() > 0) { processDeviceInfos(); processProfilingPoints(); } testRuns.sort((t1, t2) -> new Long(t2.getStartTimestamp()).compareTo(t1.getStartTimestamp())); generateToTBreakdown(); headerRow = new String[testRuns.size() + 1]; headerRow[0] = StringUtils.join(HEADER_NAMES, "
"); summaryGrid = new String[SUMMARY_NAMES.length][testRuns.size() + 1]; for (int i = 0; i < SUMMARY_NAMES.length; i++) { summaryGrid[i][0] = "" + SUMMARY_NAMES[i] + ""; } timeGrid = new String[TIME_INFO_NAMES.length][testRuns.size() + 1]; for (int i = 0; i < TIME_INFO_NAMES.length; i++) { timeGrid[i][0] = "" + TIME_INFO_NAMES[i] + ""; } durationGrid = new String[DURATION_INFO_NAMES.length][testRuns.size() + 1]; for (int i = 0; i < DURATION_INFO_NAMES.length; i++) { durationGrid[i][0] = "" + DURATION_INFO_NAMES[i] + ""; } resultsGrid = new String[testCaseNameMap.size()][testRuns.size() + 1]; // first column for results grid for (String testCaseName : testCaseNameMap.keySet()) { resultsGrid[testCaseNameMap.get(testCaseName)][0] = testCaseName; } // Iterate through the test runs for (int col = 0; col < testRuns.size(); col++) { TestRunEntity testRun = testRuns.get(col); CodeCoverageEntity codeCoverageEntity = testRun.getCodeCoverageEntity(); // Process the device information List devices = deviceInfoMap.get(testRun.getKey()); List buildIdList = new ArrayList<>(); List buildAliasList = new ArrayList<>(); List buildFlavorList = new ArrayList<>(); List productVariantList = new ArrayList<>(); List abiInfoList = new ArrayList<>(); for (DeviceInfoEntity deviceInfoEntity : devices) { buildAliasList.add(deviceInfoEntity.getBranch()); buildFlavorList.add(deviceInfoEntity.getBuildFlavor()); productVariantList.add(deviceInfoEntity.getProduct()); buildIdList.add(deviceInfoEntity.getBuildId()); String abi = ""; String abiName = deviceInfoEntity.getAbiName(); String abiBitness = deviceInfoEntity.getAbiBitness(); if (abiName.length() > 0) { abi += abiName; if (abiBitness.length() > 0) { abi += " (" + abiBitness + " bit)"; } } abiInfoList.add(abi); } String buildAlias = StringUtils.join(buildAliasList, ","); String buildFlavor = StringUtils.join(buildFlavorList, ","); String productVariant = StringUtils.join(productVariantList, ","); String buildIds = StringUtils.join(buildIdList, ","); String abiInfo = StringUtils.join(abiInfoList, ","); String vtsBuildId = testRun.getTestBuildId(); int totalCount = 0; int passCount = (int) testRun.getPassCount(); int nonpassCount = (int) testRun.getFailCount(); TestCaseResult aggregateStatus = TestCaseResult.UNKNOWN_RESULT; long totalLineCount = 0; long coveredLineCount = 0; if (testRun.getHasCodeCoverage()) { totalLineCount = codeCoverageEntity.getTotalLineCount(); coveredLineCount = codeCoverageEntity.getCoveredLineCount(); } // Process test case results for (TestCaseRunEntity testCaseEntity : testCaseRunMap.get(testRun.getKey())) { // Update the aggregated test run status totalCount += testCaseEntity.testCases.size(); for (TestCase testCase : testCaseEntity.testCases) { int result = testCase.result; String name = testCase.name; if (result == TestCaseResult.TEST_CASE_RESULT_PASS.getNumber()) { if (aggregateStatus == TestCaseResult.UNKNOWN_RESULT) { aggregateStatus = TestCaseResult.TEST_CASE_RESULT_PASS; } } else if (result != TestCaseResult.TEST_CASE_RESULT_SKIP.getNumber()) { aggregateStatus = TestCaseResult.TEST_CASE_RESULT_FAIL; } String systraceUrl = null; if (testCaseEntity.getSystraceUrl() != null) { String url = testCaseEntity.getSystraceUrl(); LinkDisplay validatedLink = UrlUtil.processUrl(url); if (validatedLink != null) { systraceUrl = validatedLink.url; } else { logger.log(Level.WARNING, "Invalid systrace URL : " + url); } } int index = testCaseNameMap.get(name); String classNames = "test-case-status "; String glyph = ""; TestCaseResult testCaseResult = TestCaseResult.valueOf(result); if (testCaseResult != null) classNames += testCaseResult.toString(); else classNames += TestCaseResult.UNKNOWN_RESULT.toString(); if (systraceUrl != null) { classNames += " width-1"; glyph += "" + "info_outline"; } resultsGrid[index][col + 1] = "
 
" + glyph; } } String passInfo; try { double passPct = Math.round((100 * passCount / (passCount + nonpassCount)) * 100f) / 100f; passInfo = Double.toString(passPct) + "%"; } catch (ArithmeticException e) { passInfo = " - "; } // Process coverage metadata String coverageInfo; String coveragePctInfo; try { double coveragePct = Math.round((100 * coveredLineCount / totalLineCount) * 100f) / 100f; coveragePctInfo = Double.toString(coveragePct) + "%" + "" + "menu"; coverageInfo = coveredLineCount + "/" + totalLineCount; } catch (ArithmeticException e) { coveragePctInfo = " - "; coverageInfo = " - "; } // Process log information String linkSummary = " - "; List linkEntries = new ArrayList<>(); logInfoMap.put(Integer.toString(col), linkEntries); if (testRun.getLogLinks() != null) { for (String rawUrl : testRun.getLogLinks()) { LinkDisplay validatedLink = UrlUtil.processUrl(rawUrl); if (validatedLink == null) { logger.log(Level.WARNING, "Invalid logging URL : " + rawUrl); continue; } String[] logInfo = new String[] { validatedLink.name, validatedLink.url // TODO: process the name from the URL }; linkEntries.add(logInfo); } } if (linkEntries.size() > 0) { linkSummary = Integer.toString(linkEntries.size()); linkSummary += "launch"; } String icon = "
 
"; String hostname = testRun.getHostName(); // Populate the header row headerRow[col + 1] = "" + buildIds + "" + icon + "" + buildAlias + "
" + buildFlavor + "
" + productVariant + "
" + abiInfo + "
" + vtsBuildId + "
" + hostname; // Populate the test summary grid summaryGrid[0][col + 1] = Integer.toString(totalCount); summaryGrid[1][col + 1] = Integer.toString(passCount); summaryGrid[2][col + 1] = Integer.toString(nonpassCount); summaryGrid[3][col + 1] = passInfo; summaryGrid[4][col + 1] = coverageInfo; summaryGrid[5][col + 1] = coveragePctInfo; summaryGrid[6][col + 1] = linkSummary; // Populate the test time info grid timeGrid[0][col + 1] = Long.toString(testRun.getStartTimestamp()); timeGrid[1][col + 1] = Long.toString(testRun.getEndTimestamp()); // Populate the test duration info grid durationGrid[0][col + 1] = Long.toString(testRun.getEndTimestamp() - testRun.getStartTimestamp()); } profilingPointNames = profilingPointNameSet.toArray(new String[profilingPointNameSet.size()]); Arrays.sort(profilingPointNames); } }