1 /* 2 * Copyright (c) 2017 Google Inc. All Rights Reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you 5 * may not use this file except in compliance with the License. You may 6 * obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 13 * implied. See the License for the specific language governing 14 * permissions and limitations under the License. 15 */ 16 17 package com.android.vts.entity; 18 19 import com.android.vts.util.UrlUtil; 20 import com.android.vts.util.UrlUtil.LinkDisplay; 21 import com.google.appengine.api.datastore.Entity; 22 import com.google.appengine.api.datastore.Key; 23 import com.google.appengine.api.datastore.KeyFactory; 24 import com.google.gson.Gson; 25 import com.google.gson.JsonElement; 26 import com.google.gson.JsonObject; 27 import com.google.gson.JsonPrimitive; 28 import java.util.ArrayList; 29 import java.util.List; 30 import java.util.logging.Level; 31 import java.util.logging.Logger; 32 33 /** Entity describing test run information. */ 34 public class TestRunEntity implements DashboardEntity { 35 protected static final Logger logger = Logger.getLogger(TestRunEntity.class.getName()); 36 37 /** Enum for classifying test run types. */ 38 public enum TestRunType { 39 OTHER(0), 40 PRESUBMIT(1), 41 POSTSUBMIT(2); 42 43 private final int value; 44 45 private TestRunType(int value) { 46 this.value = value; 47 } 48 49 /** 50 * Get the ordinal representation of the type. 51 * 52 * @return The value associated with the test run type. 53 */ 54 public int getNumber() { 55 return value; 56 } 57 58 /** 59 * Convert an ordinal value to a TestRunType. 60 * 61 * @param value The orginal value to parse. 62 * @return a TestRunType value. 63 */ 64 public static TestRunType fromNumber(int value) { 65 if (value == 1) { 66 return TestRunType.PRESUBMIT; 67 } else if (value == 2) { 68 return TestRunType.POSTSUBMIT; 69 } else { 70 return TestRunType.OTHER; 71 } 72 } 73 74 /** 75 * Determine the test run type based on the build ID. 76 * 77 * Postsubmit runs are expected to have integer build IDs, while presubmit runs are integers 78 * prefixed by the character P. All other runs (e.g. local builds) are classified as OTHER. 79 * 80 * @param buildId The build ID. 81 * @return the TestRunType. 82 */ 83 public static TestRunType fromBuildId(String buildId) { 84 try { 85 Integer.parseInt(buildId); 86 return TestRunType.POSTSUBMIT; 87 } catch (NumberFormatException e) { 88 // Not an integer 89 } 90 if (Character.toLowerCase(buildId.charAt(0)) == 'p') { 91 try { 92 Integer.parseInt(buildId.substring(1)); 93 return TestRunType.PRESUBMIT; 94 } catch (NumberFormatException e) { 95 // Not an integer 96 } 97 } 98 return TestRunType.OTHER; 99 } 100 } 101 102 public static final String KIND = "TestRun"; 103 104 // Property keys 105 public static final String TEST_NAME = "testName"; 106 public static final String TYPE = "type"; 107 public static final String START_TIMESTAMP = "startTimestamp"; 108 public static final String END_TIMESTAMP = "endTimestamp"; 109 public static final String TEST_BUILD_ID = "testBuildId"; 110 public static final String HOST_NAME = "hostName"; 111 public static final String PASS_COUNT = "passCount"; 112 public static final String FAIL_COUNT = "failCount"; 113 public static final String TEST_CASE_IDS = "testCaseIds"; 114 public static final String LOG_LINKS = "logLinks"; 115 public static final String HAS_COVERAGE = "hasCoverage"; 116 public static final String TOTAL_LINE_COUNT = "totalLineCount"; 117 public static final String COVERED_LINE_COUNT = "coveredLineCount"; 118 119 public final Key key; 120 public final TestRunType type; 121 public final long startTimestamp; 122 public final long endTimestamp; 123 public final String testBuildId; 124 public final String hostName; 125 public final long passCount; 126 public final long failCount; 127 public final boolean hasCoverage; 128 public final long coveredLineCount; 129 public final long totalLineCount; 130 public final List<Long> testCaseIds; 131 public final List<String> links; 132 133 /** 134 * Create a TestRunEntity object describing a test run. 135 * 136 * @param parentKey The key to the parent TestEntity. 137 * @param type The test run type (e.g. presubmit, postsubmit, other) 138 * @param startTimestamp The time in microseconds when the test run started. 139 * @param endTimestamp The time in microseconds when the test run ended. 140 * @param testBuildId The build ID of the VTS test build. 141 * @param hostName The name of host machine. 142 * @param passCount The number of passing test cases in the run. 143 * @param failCount The number of failing test cases in the run. 144 * @param testCaseIds A list of key IDs to the TestCaseRunEntity objects for the test run. 145 * @param links A list of links to resource files for the test run, or null if there aren't any. 146 * @param coveredLineCount The number of lines covered by the test run. 147 * @param totalLineCount The total number of executable lines by the test in the test run. 148 */ 149 public TestRunEntity(Key parentKey, TestRunType type, long startTimestamp, long endTimestamp, 150 String testBuildId, String hostName, long passCount, long failCount, 151 List<Long> testCaseIds, List<String> links, long coveredLineCount, 152 long totalLineCount) { 153 this.key = KeyFactory.createKey(parentKey, KIND, startTimestamp); 154 this.type = type; 155 this.startTimestamp = startTimestamp; 156 this.endTimestamp = endTimestamp; 157 this.testBuildId = testBuildId; 158 this.hostName = hostName; 159 this.passCount = passCount; 160 this.failCount = failCount; 161 this.testCaseIds = testCaseIds == null ? new ArrayList<Long>() : testCaseIds; 162 this.links = links == null ? new ArrayList<String>() : links; 163 this.coveredLineCount = coveredLineCount; 164 this.totalLineCount = totalLineCount; 165 this.hasCoverage = totalLineCount > 0; 166 } 167 168 /** 169 * Create a TestRunEntity object describing a test run. 170 * 171 * @param parentKey The key to the parent TestEntity. 172 * @param type The test run type (e.g. presubmit, postsubmit, other) 173 * @param startTimestamp The time in microseconds when the test run started. 174 * @param endTimestamp The time in microseconds when the test run ended. 175 * @param testBuildId The build ID of the VTS test build. 176 * @param hostName The name of host machine. 177 * @param passCount The number of passing test cases in the run. 178 * @param failCount The number of failing test cases in the run. 179 * @param testCaseIds A list of key IDs to the TestCaseRunEntity objects for the test run. 180 * @param links A list of links to resource files for the test run, or null if there aren't any. 181 */ 182 public TestRunEntity(Key parentKey, TestRunType type, long startTimestamp, long endTimestamp, 183 String testBuildId, String hostName, long passCount, long failCount, 184 List<Long> testCaseIds, List<String> links) { 185 this(parentKey, type, startTimestamp, endTimestamp, testBuildId, hostName, passCount, 186 failCount, testCaseIds, links, 0, 0); 187 } 188 189 @Override 190 public Entity toEntity() { 191 Entity testRunEntity = new Entity(this.key); 192 testRunEntity.setProperty(TEST_NAME, this.key.getParent().getName()); 193 testRunEntity.setProperty(TYPE, this.type.getNumber()); 194 testRunEntity.setProperty(START_TIMESTAMP, this.startTimestamp); 195 testRunEntity.setUnindexedProperty(END_TIMESTAMP, this.endTimestamp); 196 testRunEntity.setProperty(TEST_BUILD_ID, this.testBuildId.toLowerCase()); 197 testRunEntity.setProperty(HOST_NAME, this.hostName.toLowerCase()); 198 testRunEntity.setProperty(PASS_COUNT, this.passCount); 199 testRunEntity.setProperty(FAIL_COUNT, this.failCount); 200 testRunEntity.setUnindexedProperty(TEST_CASE_IDS, this.testCaseIds); 201 boolean hasCoverage = this.totalLineCount > 0 && this.coveredLineCount >= 0; 202 testRunEntity.setProperty(HAS_COVERAGE, hasCoverage); 203 if (hasCoverage) { 204 testRunEntity.setProperty(COVERED_LINE_COUNT, this.coveredLineCount); 205 testRunEntity.setProperty(TOTAL_LINE_COUNT, this.totalLineCount); 206 } 207 if (this.links != null && this.links.size() > 0) { 208 testRunEntity.setUnindexedProperty(LOG_LINKS, this.links); 209 } 210 return testRunEntity; 211 } 212 213 /** 214 * Convert an Entity object to a TestRunEntity. 215 * 216 * @param e The entity to process. 217 * @return TestRunEntity object with the properties from e processed, or null if incompatible. 218 */ 219 @SuppressWarnings("unchecked") 220 public static TestRunEntity fromEntity(Entity e) { 221 if (!e.getKind().equals(KIND) || !e.hasProperty(TYPE) || !e.hasProperty(START_TIMESTAMP) 222 || !e.hasProperty(END_TIMESTAMP) || !e.hasProperty(TEST_BUILD_ID) 223 || !e.hasProperty(HOST_NAME) || !e.hasProperty(PASS_COUNT) 224 || !e.hasProperty(FAIL_COUNT) || !e.hasProperty(TEST_CASE_IDS)) { 225 logger.log(Level.WARNING, "Missing test run attributes in entity: " + e.toString()); 226 return null; 227 } 228 try { 229 TestRunType type = TestRunType.fromNumber((int) (long) e.getProperty(TYPE)); 230 long startTimestamp = (long) e.getProperty(START_TIMESTAMP); 231 long endTimestamp = (long) e.getProperty(END_TIMESTAMP); 232 String testBuildId = (String) e.getProperty(TEST_BUILD_ID); 233 String hostName = (String) e.getProperty(HOST_NAME); 234 long passCount = (long) e.getProperty(PASS_COUNT); 235 long failCount = (long) e.getProperty(FAIL_COUNT); 236 List<Long> testCaseIds = (List<Long>) e.getProperty(TEST_CASE_IDS); 237 List<String> links = null; 238 long coveredLineCount = 0; 239 long totalLineCount = 0; 240 if (e.hasProperty(TOTAL_LINE_COUNT) && e.hasProperty(COVERED_LINE_COUNT)) { 241 coveredLineCount = (long) e.getProperty(COVERED_LINE_COUNT); 242 totalLineCount = (long) e.getProperty(TOTAL_LINE_COUNT); 243 } 244 if (e.hasProperty(LOG_LINKS)) { 245 links = (List<String>) e.getProperty(LOG_LINKS); 246 } 247 return new TestRunEntity(e.getKey().getParent(), type, startTimestamp, endTimestamp, 248 testBuildId, hostName, passCount, failCount, testCaseIds, links, 249 coveredLineCount, totalLineCount); 250 } catch (ClassCastException exception) { 251 // Invalid cast 252 logger.log(Level.WARNING, "Error parsing test run entity.", exception); 253 } 254 return null; 255 } 256 257 public JsonObject toJson() { 258 JsonObject json = new JsonObject(); 259 json.add(TEST_NAME, new JsonPrimitive(this.key.getParent().getName())); 260 json.add(TEST_BUILD_ID, new JsonPrimitive(this.testBuildId)); 261 json.add(HOST_NAME, new JsonPrimitive(this.hostName)); 262 json.add(PASS_COUNT, new JsonPrimitive(this.passCount)); 263 json.add(FAIL_COUNT, new JsonPrimitive(this.failCount)); 264 json.add(START_TIMESTAMP, new JsonPrimitive(this.startTimestamp)); 265 json.add(END_TIMESTAMP, new JsonPrimitive(this.endTimestamp)); 266 if (this.totalLineCount > 0 && this.coveredLineCount >= 0) { 267 json.add(COVERED_LINE_COUNT, new JsonPrimitive(this.coveredLineCount)); 268 json.add(TOTAL_LINE_COUNT, new JsonPrimitive(this.totalLineCount)); 269 } 270 if (this.links != null && this.links.size() > 0) { 271 List<JsonElement> links = new ArrayList<>(); 272 for (String rawUrl : this.links) { 273 LinkDisplay validatedLink = UrlUtil.processUrl(rawUrl); 274 if (validatedLink == null) { 275 logger.log(Level.WARNING, "Invalid logging URL : " + rawUrl); 276 continue; 277 } 278 String[] logInfo = new String[] {validatedLink.name, validatedLink.url}; 279 links.add(new Gson().toJsonTree(logInfo)); 280 } 281 if (links.size() > 0) { 282 json.add(LOG_LINKS, new Gson().toJsonTree(links)); 283 } 284 } 285 return json; 286 } 287 } 288