blob: adb2a5a8e28d1c951e76412b61429652995b17ed [file] [log] [blame]
/*
* 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<TestRunEntity> testRuns; // list of all test runs
private Map<Key, List<TestCaseRunEntity>>
testCaseRunMap; // map from test run key to the test run information
private Map<Key, List<DeviceInfoEntity>> deviceInfoMap; // map from test run key to device info
private Map<String, Integer> testCaseNameMap; // map from test case name to its order
private Set<String> 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<String, List<String[]>> 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 = {"<b>Test Duration</b>"};
// 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 = {
"<b>Stats Type \\ Device Build ID</b>",
"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<Entity> 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<TestCaseRunEntity>());
// 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<TestCaseRunEntity> testCaseResults = testCaseRunMap.get(mostRecentRun.getKey());
List<DeviceInfoEntity> 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<Entity> 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<Key> deviceGets = new ArrayList<>();
for (Entity device :
datastore.prepare(deviceQuery).asIterable(DatastoreHelper.getLargeBatchOptions())) {
if (testCaseRunMap.containsKey(device.getParent())) {
deviceGets.add(device.getKey());
}
}
Map<Key, Entity> 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<DeviceInfoEntity>());
}
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, "<br>");
summaryGrid = new String[SUMMARY_NAMES.length][testRuns.size() + 1];
for (int i = 0; i < SUMMARY_NAMES.length; i++) {
summaryGrid[i][0] = "<b>" + SUMMARY_NAMES[i] + "</b>";
}
timeGrid = new String[TIME_INFO_NAMES.length][testRuns.size() + 1];
for (int i = 0; i < TIME_INFO_NAMES.length; i++) {
timeGrid[i][0] = "<b>" + TIME_INFO_NAMES[i] + "</b>";
}
durationGrid = new String[DURATION_INFO_NAMES.length][testRuns.size() + 1];
for (int i = 0; i < DURATION_INFO_NAMES.length; i++) {
durationGrid[i][0] = "<b>" + DURATION_INFO_NAMES[i] + "</b>";
}
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<DeviceInfoEntity> devices = deviceInfoMap.get(testRun.getKey());
List<String> buildIdList = new ArrayList<>();
List<String> buildAliasList = new ArrayList<>();
List<String> buildFlavorList = new ArrayList<>();
List<String> productVariantList = new ArrayList<>();
List<String> 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 +=
"<a href=\""
+ systraceUrl
+ "\" "
+ "class=\"waves-effect waves-light btn red right inline-btn\">"
+ "<i class=\"material-icons inline-icon\">info_outline</i></a>";
}
resultsGrid[index][col + 1] =
"<div class=\"" + classNames + "\">&nbsp;</div>" + 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)
+ "%"
+ "<a href=\"/show_coverage?testName="
+ testName
+ "&startTime="
+ testRun.getStartTimestamp()
+ "\" class=\"waves-effect waves-light btn red right inline-btn\">"
+ "<i class=\"material-icons inline-icon\">menu</i></a>";
coverageInfo = coveredLineCount + "/" + totalLineCount;
} catch (ArithmeticException e) {
coveragePctInfo = " - ";
coverageInfo = " - ";
}
// Process log information
String linkSummary = " - ";
List<String[]> 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 +=
"<i class=\"waves-effect waves-light btn red right inline-btn"
+ " info-btn material-icons inline-icon\""
+ " data-col=\""
+ Integer.toString(col)
+ "\""
+ ">launch</i>";
}
String icon = "<div class='status-icon " + aggregateStatus.toString() + "'>&nbsp</div>";
String hostname = testRun.getHostName();
// Populate the header row
headerRow[col + 1] =
"<span class='valign-wrapper'><b>"
+ buildIds
+ "</b>"
+ icon
+ "</span>"
+ buildAlias
+ "<br>"
+ buildFlavor
+ "<br>"
+ productVariant
+ "<br>"
+ abiInfo
+ "<br>"
+ vtsBuildId
+ "<br>"
+ 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);
}
}