blob: 2ed387b9d38bd4225c9df343db9de053ebf1fd5e [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.api;
import com.android.vts.entity.BranchEntity;
import com.android.vts.entity.BuildTargetEntity;
import com.android.vts.entity.CodeCoverageEntity;
import com.android.vts.entity.CoverageEntity;
import com.android.vts.entity.DeviceInfoEntity;
import com.android.vts.entity.ProfilingPointRunEntity;
import com.android.vts.entity.TestCaseRunEntity;
import com.android.vts.entity.TestEntity;
import com.android.vts.entity.TestPlanEntity;
import com.android.vts.entity.TestPlanRunEntity;
import com.android.vts.entity.TestRunEntity;
import com.android.vts.entity.TestStatusEntity;
import com.android.vts.entity.TestStatusEntity.TestCaseReference;
import com.android.vts.entity.TestSuiteFileEntity;
import com.android.vts.entity.TestSuiteResultEntity;
import com.google.appengine.api.datastore.DatastoreFailureException;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.DatastoreTimeoutException;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Transaction;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.appengine.api.utils.SystemProperty;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.IntStream;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/** Servlet for handling requests to add mock data in datastore. */
public class TestDataForDevServlet extends HttpServlet {
protected static final Logger logger = Logger.getLogger(TestDataForDevServlet.class.getName());
/** Google Cloud Storage project's default directory name for suite test result files */
private static String GCS_SUITE_TEST_FOLDER_NAME;
/** datastore instance to save the test data into datastore through datastore library. */
private DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
/**
* Gson is a Java library that can be used to convert Java Objects into their JSON
* representation. It can also be used to convert a JSON string to an equivalent Java object.
*/
private Gson gson = new GsonBuilder().create();
/** System Configuration Property class */
protected Properties systemConfigProp = new Properties();
@Override
public void init(ServletConfig cfg) throws ServletException {
super.init(cfg);
try {
InputStream defaultInputStream =
TestDataForDevServlet.class
.getClassLoader()
.getResourceAsStream("config.properties");
systemConfigProp.load(defaultInputStream);
GCS_SUITE_TEST_FOLDER_NAME = systemConfigProp.getProperty("gcs.suiteTestFolderName");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* TestReportData class for mapping test-report-data.json. This internal class's each fields
* will be automatically mapped to test-report-data.json file through Gson
*/
private class TestReportDataObject {
private List<Test> testList;
private class Test {
private List<TestRun> testRunList;
private class TestRun {
private String testName;
private int type;
private long startTimestamp;
private long endTimestamp;
private String testBuildId;
private String hostName;
private long passCount;
private long failCount;
private boolean hasCoverage;
private long coveredLineCount;
private long totalLineCount;
private List<Long> testCaseIds;
private List<Long> failingTestcaseIds;
private List<Integer> failingTestcaseOffsets;
private List<String> links;
private List<Coverage> coverageList;
private List<Profiling> profilingList;
private List<TestCaseRun> testCaseRunList;
private List<DeviceInfo> deviceInfoList;
private List<BuildTarget> buildTargetList;
private List<Branch> branchList;
private class Coverage {
private String group;
private long coveredLineCount;
private long totalLineCount;
private String filePath;
private String projectName;
private String projectVersion;
private List<Long> lineCoverage;
}
private class Profiling {
private String name;
private int type;
private int regressionMode;
private List<String> labels;
private List<Long> values;
private String xLabel;
private String yLabel;
private List<String> options;
}
private class TestCaseRun {
private List<String> testCaseNames;
private List<Integer> results;
}
private class DeviceInfo {
private String branch;
private String product;
private String buildFlavor;
private String buildId;
private String abiBitness;
private String abiName;
}
private class BuildTarget {
private String targetName;
}
private class Branch {
private String branchName;
}
}
}
@Override
public String toString() {
return "(" + testList + ")";
}
}
private class TestPlanReportDataObject {
private List<TestPlan> testPlanList;
private class TestPlan {
private String testPlanName;
private List<String> testModules;
private List<Long> testTimes;
}
@Override
public String toString() {
return "(" + testPlanList + ")";
}
}
private Map<String, Object> generateSuiteTestData(
HttpServletRequest request, HttpServletResponse response) {
Map<String, Object> resultMap = new HashMap<>();
String fileSeparator = FileSystems.getDefault().getSeparator();
Random rand = new Random();
List<String> branchList = Arrays.asList("master", "oc_mr", "oc");
List<String> targetList =
Arrays.asList(
"sailfish-userdebug",
"marlin-userdebug",
"taimen-userdebug",
"walleye-userdebug",
"aosp_arm_a-userdebug");
branchList.forEach(
branch ->
targetList.forEach(
target ->
IntStream.range(0, 10)
.forEach(
idx -> {
String year =
String.format(
"%04d", 2010 + idx);
String month =
String.format(
"%02d",
rand.nextInt(12));
String day =
String.format(
"%02d",
rand.nextInt(30));
String fileName =
String.format(
"%02d%02d%02d.bin",
rand.nextInt(23) + 1,
rand.nextInt(59) + 1,
rand.nextInt(59) + 1);
List<String> pathList =
Arrays.asList(
GCS_SUITE_TEST_FOLDER_NAME
== ""
? "suite_result"
: GCS_SUITE_TEST_FOLDER_NAME,
year,
month,
day,
fileName);
Path pathInfo =
Paths.get(
String.join(
fileSeparator,
pathList));
TestSuiteFileEntity
newTestSuiteFileEntity =
new TestSuiteFileEntity(
pathInfo
.toString());
com.googlecode.objectify.Key<
TestSuiteFileEntity>
testSuiteFileParent =
com.googlecode.objectify
.Key.create(
TestSuiteFileEntity
.class,
newTestSuiteFileEntity
.getFilePath());
TestSuiteResultEntity
testSuiteResultEntity =
new TestSuiteResultEntity(
testSuiteFileParent,
Instant.now()
.minus(
rand
.nextInt(
100),
ChronoUnit
.DAYS)
.getEpochSecond(),
Instant.now()
.minus(
rand
.nextInt(
100),
ChronoUnit
.DAYS)
.getEpochSecond(),
1,
idx / 2 == 0
? false
: true,
pathInfo
.toString(),
idx / 2 == 0
? "/error/infra/log"
: "",
"Test Place Name -"
+ idx,
"Suite Test Plan",
"Suite Version "
+ idx,
"Suite Test Name",
"Suite Build Number "
+ idx,
rand.nextInt(),
rand.nextInt(),
branch,
target,
Long.toString(
Math
.abs(
rand
.nextLong())),
"Build System Fingerprint "
+ idx,
"Build Vendor Fingerprint "
+ idx,
rand.nextInt(),
rand.nextInt());
testSuiteResultEntity.save(newTestSuiteFileEntity);
})));
resultMap.put("result", "successfully generated!");
return resultMap;
}
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
String requestUri = request.getRequestURI();
String requestArgs = request.getQueryString();
Map<String, Object> resultMap = new HashMap<>();
String pathInfo = requestUri.replace("/api/test_data/", "");
switch (pathInfo) {
case "suite":
resultMap = this.generateSuiteTestData(request, response);
break;
default:
throw new IllegalArgumentException("Invalid path info of URL");
}
String json = new Gson().toJson(resultMap);
response.setStatus(HttpServletResponse.SC_OK);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(json);
}
/** Add mock data to local dev datastore. */
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException {
if (SystemProperty.environment.value() == SystemProperty.Environment.Value.Production) {
response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
return;
}
UserService userService = UserServiceFactory.getUserService();
User currentUser = userService.getCurrentUser();
String pathInfo = request.getPathInfo();
String[] pathParts = pathInfo.split("/");
if (pathParts.length > 1) {
// Read the json output
Reader postJsonReader = new InputStreamReader(request.getInputStream());
Gson gson = new GsonBuilder().create();
String testType = pathParts[1];
if (testType.equalsIgnoreCase("report")) {
TestReportDataObject trdObj =
gson.fromJson(postJsonReader, TestReportDataObject.class);
logger.log(Level.INFO, "trdObj => " + trdObj);
trdObj.testList.forEach(
test -> {
test.testRunList.forEach(
testRun -> {
TestEntity testEntity = new TestEntity(testRun.testName);
Key testRunKey =
KeyFactory.createKey(
testEntity.getOldKey(),
TestRunEntity.KIND,
testRun.startTimestamp);
List<TestCaseReference> failingTestCases =
new ArrayList<>();
for (int idx = 0;
idx < testRun.failingTestcaseIds.size();
idx++) {
failingTestCases.add(
new TestCaseReference(
testRun.failingTestcaseIds.get(idx),
testRun.failingTestcaseOffsets.get(
idx)));
}
TestStatusEntity testStatusEntity =
new TestStatusEntity(
testRun.testName,
testRun.startTimestamp,
(int) testRun.passCount,
failingTestCases.size(),
failingTestCases);
datastore.put(testStatusEntity.toEntity());
testRun.coverageList.forEach(
testRunCoverage -> {
CoverageEntity coverageEntity =
new CoverageEntity(
testRunKey,
testRunCoverage.group,
testRunCoverage
.coveredLineCount,
testRunCoverage.totalLineCount,
testRunCoverage.filePath,
testRunCoverage.projectName,
testRunCoverage.projectVersion,
testRunCoverage.lineCoverage);
datastore.put(coverageEntity.toEntity());
});
testRun.profilingList.forEach(
testRunProfile -> {
ProfilingPointRunEntity profilingEntity =
new ProfilingPointRunEntity(
testRunKey,
testRunProfile.name,
testRunProfile.type,
testRunProfile.regressionMode,
testRunProfile.labels,
testRunProfile.values,
testRunProfile.xLabel,
testRunProfile.yLabel,
testRunProfile.options);
datastore.put(profilingEntity.toEntity());
});
TestCaseRunEntity testCaseEntity = new TestCaseRunEntity();
testRun.testCaseRunList.forEach(
testCaseRun -> {
for (int idx = 0;
idx < testCaseRun.testCaseNames.size();
idx++) {
testCaseEntity.addTestCase(
testCaseRun.testCaseNames.get(idx),
testCaseRun.results.get(idx));
}
});
datastore.put(testCaseEntity.toEntity());
testRun.deviceInfoList.forEach(
deviceInfo -> {
DeviceInfoEntity deviceInfoEntity =
new DeviceInfoEntity(
testRunKey,
deviceInfo.branch,
deviceInfo.product,
deviceInfo.buildFlavor,
deviceInfo.buildId,
deviceInfo.abiBitness,
deviceInfo.abiName);
;
datastore.put(deviceInfoEntity.toEntity());
});
testRun.buildTargetList.forEach(
buildTarget -> {
BuildTargetEntity buildTargetEntity =
new BuildTargetEntity(
buildTarget.targetName);
buildTargetEntity.save();
});
testRun.branchList.forEach(
branch -> {
BranchEntity branchEntity =
new BranchEntity(branch.branchName);
branchEntity.save();
});
boolean hasCodeCoverage =
testRun.totalLineCount > 0
&& testRun.coveredLineCount >= 0;
TestRunEntity testRunEntity =
new TestRunEntity(
testEntity.getOldKey(),
testRun.type,
testRun.startTimestamp,
testRun.endTimestamp,
testRun.testBuildId,
testRun.hostName,
testRun.passCount,
testRun.failCount,
hasCodeCoverage,
testRun.testCaseIds,
testRun.links);
datastore.put(testRunEntity.toEntity());
CodeCoverageEntity codeCoverageEntity =
new CodeCoverageEntity(
testRunEntity.getKey(),
testRun.coveredLineCount,
testRun.totalLineCount);
datastore.put(codeCoverageEntity.toEntity());
Entity newTestEntity = testEntity.toEntity();
Transaction txn = datastore.beginTransaction();
try {
// Check if test already exists in the datastore
try {
Entity oldTest =
datastore.get(testEntity.getOldKey());
TestEntity oldTestEntity =
TestEntity.fromEntity(oldTest);
if (oldTestEntity == null
|| !oldTestEntity.equals(testEntity)) {
datastore.put(newTestEntity);
}
} catch (EntityNotFoundException e) {
datastore.put(newTestEntity);
}
txn.commit();
} catch (ConcurrentModificationException
| DatastoreFailureException
| DatastoreTimeoutException e) {
logger.log(
Level.WARNING,
"Retrying test run insert: "
+ newTestEntity.getKey());
} finally {
if (txn.isActive()) {
logger.log(
Level.WARNING,
"Transaction rollback forced for run: "
+ testRunEntity.getKey());
txn.rollback();
}
}
});
});
} else {
TestPlanReportDataObject tprdObj =
gson.fromJson(postJsonReader, TestPlanReportDataObject.class);
tprdObj.testPlanList.forEach(
testPlan -> {
Entity testPlanEntity =
new TestPlanEntity(testPlan.testPlanName).toEntity();
List<Key> testRunKeys = new ArrayList<>();
for (int idx = 0; idx < testPlan.testModules.size(); idx++) {
String test = testPlan.testModules.get(idx);
long time = testPlan.testTimes.get(idx);
Key parentKey = KeyFactory.createKey(TestEntity.KIND, test);
Key testRunKey =
KeyFactory.createKey(parentKey, TestRunEntity.KIND, time);
testRunKeys.add(testRunKey);
}
Map<Key, Entity> testRuns = datastore.get(testRunKeys);
long passCount = 0;
long failCount = 0;
long startTimestamp = -1;
long endTimestamp = -1;
String testBuildId = null;
long type = 0;
Set<DeviceInfoEntity> devices = new HashSet<>();
for (Key testRunKey : testRuns.keySet()) {
TestRunEntity testRun =
TestRunEntity.fromEntity(testRuns.get(testRunKey));
if (testRun == null) {
continue; // not a valid test run
}
passCount += testRun.getPassCount();
failCount += testRun.getFailCount();
if (startTimestamp < 0 || testRunKey.getId() < startTimestamp) {
startTimestamp = testRunKey.getId();
}
if (endTimestamp < 0 || testRun.getEndTimestamp() > endTimestamp) {
endTimestamp = testRun.getEndTimestamp();
}
type = testRun.getType();
testBuildId = testRun.getTestBuildId();
Query deviceInfoQuery =
new Query(DeviceInfoEntity.KIND).setAncestor(testRunKey);
for (Entity deviceInfoEntity :
datastore.prepare(deviceInfoQuery).asIterable()) {
DeviceInfoEntity device =
DeviceInfoEntity.fromEntity(deviceInfoEntity);
if (device == null) {
continue; // invalid entity
}
devices.add(device);
}
}
if (startTimestamp < 0 || testBuildId == null || type == 0) {
logger.log(
Level.WARNING,
"Couldn't infer test run information from runs.");
return;
}
TestPlanRunEntity testPlanRun =
new TestPlanRunEntity(
testPlanEntity.getKey(),
testPlan.testPlanName,
type,
startTimestamp,
endTimestamp,
testBuildId,
passCount,
failCount,
0L,
0L,
testRunKeys);
// Create the device infos.
for (DeviceInfoEntity device : devices) {
datastore.put(
device.copyWithParent(testPlanRun.getOfyKey()).toEntity());
}
datastore.put(testPlanRun.toEntity());
Transaction txn = datastore.beginTransaction();
try {
// Check if test already exists in the database
try {
datastore.get(testPlanEntity.getKey());
} catch (EntityNotFoundException e) {
datastore.put(testPlanEntity);
}
txn.commit();
} catch (ConcurrentModificationException
| DatastoreFailureException
| DatastoreTimeoutException e) {
logger.log(
Level.WARNING,
"Retrying test plan insert: " + testPlanEntity.getKey());
} finally {
if (txn.isActive()) {
logger.log(
Level.WARNING,
"Transaction rollback forced for plan run: "
+ testPlanRun.key);
txn.rollback();
}
}
});
}
} else {
logger.log(Level.WARNING, "URL path parameter is omitted!");
}
response.setStatus(HttpServletResponse.SC_OK);
}
}