1 /** 2 * Copyright 2016 Google Inc. All Rights Reserved. 3 * 4 * <p>Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * <p>http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * <p>Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 * express or implied. See the License for the specific language governing permissions and 12 * limitations under the License. 13 */ 14 package com.android.vts.util; 15 16 import com.android.vts.entity.CoverageEntity; 17 import com.android.vts.entity.DeviceInfoEntity; 18 import com.android.vts.entity.ProfilingPointRunEntity; 19 import com.android.vts.entity.TestCaseRunEntity; 20 import com.android.vts.entity.TestEntity; 21 import com.android.vts.entity.TestRunEntity; 22 import com.android.vts.entity.TestRunEntity.TestRunType; 23 import com.android.vts.proto.VtsReportMessage.AndroidDeviceInfoMessage; 24 import com.android.vts.proto.VtsReportMessage.CoverageReportMessage; 25 import com.android.vts.proto.VtsReportMessage.LogMessage; 26 import com.android.vts.proto.VtsReportMessage.ProfilingReportMessage; 27 import com.android.vts.proto.VtsReportMessage.TestCaseReportMessage; 28 import com.android.vts.proto.VtsReportMessage.TestCaseResult; 29 import com.android.vts.proto.VtsReportMessage.TestReportMessage; 30 import com.google.appengine.api.datastore.DatastoreService; 31 import com.google.appengine.api.datastore.DatastoreServiceFactory; 32 import com.google.appengine.api.datastore.Entity; 33 import com.google.appengine.api.datastore.EntityNotFoundException; 34 import com.google.appengine.api.datastore.FetchOptions; 35 import com.google.appengine.api.datastore.Key; 36 import com.google.appengine.api.datastore.KeyFactory; 37 import com.google.appengine.api.datastore.KeyRange; 38 import com.google.appengine.api.datastore.PropertyProjection; 39 import com.google.appengine.api.datastore.Query; 40 import com.google.appengine.api.datastore.Query.Filter; 41 import com.google.appengine.api.datastore.Query.FilterOperator; 42 import com.google.appengine.api.datastore.Query.FilterPredicate; 43 import com.google.appengine.api.datastore.Transaction; 44 import java.io.IOException; 45 import java.util.ArrayList; 46 import java.util.List; 47 import java.util.logging.Level; 48 import java.util.logging.Logger; 49 50 /** DatastoreHelper, a helper class for interacting with Cloud Datastore. */ 51 public class DatastoreHelper { 52 protected static final Logger logger = Logger.getLogger(DatastoreHelper.class.getName()); 53 54 /** 55 * Returns true if there are data points newer than lowerBound in the results table. 56 * 57 * @param testName The test to check. 58 * @param lowerBound The (exclusive) lower time bound, long, microseconds. 59 * @return boolean True if there are newer data points. 60 * @throws IOException 61 */ hasNewer(String testName, long lowerBound)62 public static boolean hasNewer(String testName, long lowerBound) throws IOException { 63 if (lowerBound <= 0) 64 return false; 65 DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); 66 Key testKey = KeyFactory.createKey(TestEntity.KIND, testName); 67 Key startKey = KeyFactory.createKey(testKey, TestRunEntity.KIND, lowerBound); 68 Filter startFilter = new FilterPredicate( 69 Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, startKey); 70 Query q = new Query(TestRunEntity.KIND) 71 .setAncestor(KeyFactory.createKey(TestEntity.KIND, testName)) 72 .setFilter(startFilter) 73 .setKeysOnly(); 74 return datastore.prepare(q).countEntities(FetchOptions.Builder.withLimit(1)) > 0; 75 } 76 77 /** 78 * Returns true if there are data points older than upperBound in the table. 79 * 80 * @param testName The test to check. 81 * @param upperBound The (exclusive) upper time bound, long, microseconds. 82 * @return boolean True if there are older data points. 83 * @throws IOException 84 */ hasOlder(String testName, long upperBound)85 public static boolean hasOlder(String testName, long upperBound) throws IOException { 86 if (upperBound <= 0) 87 return false; 88 DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); 89 Key testKey = KeyFactory.createKey(TestEntity.KIND, testName); 90 Key endKey = KeyFactory.createKey(testKey, TestRunEntity.KIND, upperBound); 91 Filter endFilter = 92 new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.LESS_THAN, endKey); 93 Query q = new Query(TestRunEntity.KIND) 94 .setAncestor(KeyFactory.createKey(TestEntity.KIND, testName)) 95 .setFilter(endFilter) 96 .setKeysOnly(); 97 return datastore.prepare(q).countEntities(FetchOptions.Builder.withLimit(1)) > 0; 98 } 99 100 /** 101 * Determines if any entities match the provided query. 102 * 103 * @param query The query to test. 104 * @return true if entities match the query. 105 */ hasEntities(Query query)106 public static boolean hasEntities(Query query) { 107 DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); 108 FetchOptions limitOne = FetchOptions.Builder.withLimit(1); 109 return datastore.prepare(query).countEntities(limitOne) > 0; 110 } 111 112 /** 113 * Get all of the target product names. 114 * 115 * @param testName the name of the test whose runs to query. 116 * @return a list of all device product names. 117 */ getAllProducts(String testName)118 public static List<String> getAllProducts(String testName) { 119 DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); 120 Query query = new Query(DeviceInfoEntity.KIND) 121 .setAncestor(KeyFactory.createKey(TestEntity.KIND, testName)) 122 .addProjection(new PropertyProjection( 123 DeviceInfoEntity.PRODUCT, String.class)) 124 .setDistinct(true); 125 List<String> devices = new ArrayList<>(); 126 for (Entity e : datastore.prepare(query).asIterable()) { 127 devices.add((String) e.getProperty(DeviceInfoEntity.PRODUCT)); 128 } 129 return devices; 130 } 131 132 /** 133 * Upload data from a test report message 134 * 135 * @param report The test report containing data to upload. 136 */ insertData(TestReportMessage report)137 public static void insertData(TestReportMessage report) { 138 DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); 139 List<Entity> puts = new ArrayList<>(); 140 141 if (!report.hasStartTimestamp() || !report.hasEndTimestamp() || !report.hasTest() 142 || !report.hasHostInfo() || !report.hasBuildInfo()) { 143 // missing information 144 return; 145 } 146 long startTimestamp = report.getStartTimestamp(); 147 long endTimestamp = report.getEndTimestamp(); 148 String testName = report.getTest().toStringUtf8(); 149 String testBuildId = report.getBuildInfo().getId().toStringUtf8(); 150 String hostName = report.getHostInfo().getHostname().toStringUtf8(); 151 152 TestRunType testRunType = TestRunType.POSTSUBMIT; 153 154 Entity testEntity = new TestEntity(testName).toEntity(); 155 List<Long> testCaseIds = new ArrayList<>(); 156 157 Key testRunKey = KeyFactory.createKey( 158 testEntity.getKey(), TestRunEntity.KIND, report.getStartTimestamp()); 159 160 long passCount = 0; 161 long failCount = 0; 162 long coveredLineCount = 0; 163 long totalLineCount = 0; 164 165 // Process test cases 166 for (TestCaseReportMessage testCase : report.getTestCaseList()) { 167 String testCaseName = testCase.getName().toStringUtf8(); 168 TestCaseResult result = testCase.getTestResult(); 169 // Track global pass/fail counts 170 if (result == TestCaseResult.TEST_CASE_RESULT_PASS) { 171 ++passCount; 172 } else if (result != TestCaseResult.TEST_CASE_RESULT_SKIP) { 173 ++failCount; 174 } 175 String systraceLink = null; 176 if (testCase.getSystraceCount() > 0 177 && testCase.getSystraceList().get(0).getUrlCount() > 0) { 178 systraceLink = testCase.getSystraceList().get(0).getUrl(0).toStringUtf8(); 179 } 180 // Process coverage data for test case 181 for (CoverageReportMessage coverage : testCase.getCoverageList()) { 182 CoverageEntity coverageEntity = 183 CoverageEntity.fromCoverageReport(testRunKey, testCaseName, coverage); 184 if (coverageEntity == null) { 185 logger.log(Level.WARNING, "Invalid coverage report in test run " + testRunKey); 186 continue; 187 } 188 coveredLineCount += coverageEntity.coveredLineCount; 189 totalLineCount += coverageEntity.totalLineCount; 190 puts.add(coverageEntity.toEntity()); 191 } 192 // Process profiling data for test case 193 for (ProfilingReportMessage profiling : testCase.getProfilingList()) { 194 ProfilingPointRunEntity profilingEntity = 195 ProfilingPointRunEntity.fromProfilingReport(testRunKey, profiling); 196 if (profilingEntity == null) { 197 logger.log(Level.WARNING, "Invalid profiling report in test run " + testRunKey); 198 } 199 puts.add(profilingEntity.toEntity()); 200 } 201 KeyRange keys = datastore.allocateIds(TestCaseRunEntity.KIND, 1); 202 testCaseIds.add(keys.getStart().getId()); 203 TestCaseRunEntity testCaseRunEntity = new TestCaseRunEntity( 204 keys.getStart(), testCaseName, result.getNumber(), systraceLink); 205 datastore.put(testCaseRunEntity.toEntity()); 206 } 207 208 // Process device information 209 for (AndroidDeviceInfoMessage device : report.getDeviceInfoList()) { 210 DeviceInfoEntity deviceInfoEntity = 211 DeviceInfoEntity.fromDeviceInfoMessage(testRunKey, device); 212 if (deviceInfoEntity == null) { 213 logger.log(Level.WARNING, "Invalid device info in test run " + testRunKey); 214 } 215 if (deviceInfoEntity.buildId.charAt(0) == 'p') { 216 testRunType = TestRunType.PRESUBMIT; // pre-submit builds begin with the letter 'p' 217 } 218 puts.add(deviceInfoEntity.toEntity()); 219 } 220 221 // Process global coverage data 222 for (CoverageReportMessage coverage : report.getCoverageList()) { 223 CoverageEntity coverageEntity = 224 CoverageEntity.fromCoverageReport(testRunKey, new String(), coverage); 225 if (coverageEntity == null) { 226 logger.log(Level.WARNING, "Invalid coverage report in test run " + testRunKey); 227 continue; 228 } 229 coveredLineCount += coverageEntity.coveredLineCount; 230 totalLineCount += coverageEntity.totalLineCount; 231 puts.add(coverageEntity.toEntity()); 232 } 233 234 // Process global profiling data 235 for (ProfilingReportMessage profiling : report.getProfilingList()) { 236 ProfilingPointRunEntity profilingEntity = 237 ProfilingPointRunEntity.fromProfilingReport(testRunKey, profiling); 238 if (profilingEntity == null) { 239 logger.log(Level.WARNING, "Invalid profiling report in test run " + testRunKey); 240 } 241 puts.add(profilingEntity.toEntity()); 242 } 243 244 List<String> logLinks = new ArrayList<>(); 245 // Process log data 246 for (LogMessage log : report.getLogList()) { 247 if (!log.hasUrl()) 248 continue; 249 logLinks.add(log.getUrl().toStringUtf8()); 250 } 251 252 try { 253 Integer.parseInt(testBuildId); 254 } catch (NumberFormatException e) { 255 testRunType = TestRunType.OTHER; 256 } 257 TestRunEntity testRunEntity = new TestRunEntity(testEntity.getKey(), testRunType, 258 startTimestamp, endTimestamp, testBuildId, hostName, passCount, failCount, 259 testCaseIds, logLinks, coveredLineCount, totalLineCount); 260 puts.add(testRunEntity.toEntity()); 261 262 Transaction txn = datastore.beginTransaction(); 263 try { 264 // Check if test already exists in the database 265 try { 266 datastore.get(testEntity.getKey()); 267 } catch (EntityNotFoundException e) { 268 puts.add(testEntity); 269 } 270 datastore.put(puts); 271 txn.commit(); 272 } finally { 273 if (txn.isActive()) { 274 logger.log( 275 Level.WARNING, "Transaction rollback forced for run: " + testRunEntity.key); 276 txn.rollback(); 277 } 278 } 279 } 280 } 281