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.entity.TestCaseRunEntity.TestCase; 20 import com.google.appengine.api.datastore.Entity; 21 import com.googlecode.objectify.annotation.Cache; 22 import com.googlecode.objectify.annotation.Id; 23 import com.googlecode.objectify.annotation.Ignore; 24 import com.googlecode.objectify.annotation.Index; 25 import java.util.ArrayList; 26 import java.util.List; 27 import java.util.logging.Level; 28 import java.util.logging.Logger; 29 import lombok.Data; 30 import lombok.NoArgsConstructor; 31 32 import static com.googlecode.objectify.ObjectifyService.ofy; 33 34 @com.googlecode.objectify.annotation.Entity(name = "TestStatus") 35 @Cache 36 @Data 37 @NoArgsConstructor 38 /** Entity describing test status. */ 39 public class TestStatusEntity implements DashboardEntity { 40 protected static final Logger logger = Logger.getLogger(TestStatusEntity.class.getName()); 41 42 public static final String KIND = "TestStatus"; 43 44 // Property keys 45 public static final String PASS_COUNT = "passCount"; 46 public static final String FAIL_COUNT = "failCount"; 47 public static final String UPDATED_TIMESTAMP = "updatedTimestamp"; 48 49 protected static final String FAILING_IDS = "failingTestcaseIds"; 50 protected static final String FAILING_OFFSETS = "failingTestcaseOffsets"; 51 52 /** ID field */ 53 @Id private String testName; 54 55 /** Failing Testcase ID List field */ 56 private List<Long> failingTestcaseIds; 57 58 /** Failing Testcase Offsets List field */ 59 private List<Integer> failingTestcaseOffsets; 60 61 /** pass count field */ 62 @Index private int passCount; 63 64 /** fail count field */ 65 @Index private int failCount; 66 67 /** updated timestamp field */ 68 @Index private long updatedTimestamp; 69 70 @Ignore private List<TestCaseReference> failingTestCases; 71 72 /** Object representing a reference to a test case. */ 73 public static class TestCaseReference { 74 public final long parentId; 75 public final int offset; 76 77 /** 78 * Create a test case reference. 79 * 80 * @param parentId The ID of the TestCaseRunEntity containing the test case. 81 * @param offset The offset of the test case into the TestCaseRunEntity. 82 */ TestCaseReference(long parentId, int offset)83 public TestCaseReference(long parentId, int offset) { 84 this.parentId = parentId; 85 this.offset = offset; 86 } 87 88 /** 89 * Create a test case reference. 90 * 91 * @param testCase The TestCase to reference. 92 */ TestCaseReference(TestCase testCase)93 public TestCaseReference(TestCase testCase) { 94 this(testCase.parentId, testCase.offset); 95 } 96 } 97 98 /** 99 * Create a TestEntity object with status metadata. 100 * 101 * @param testName The name of the test. 102 * @param timestamp The timestamp indicating the most recent test run event in the test state. 103 * @param passCount The number of tests passing up to the timestamp specified. 104 * @param failCount The number of tests failing up to the timestamp specified. 105 * @param failingTestCases The TestCaseReferences of the last observed failing test cases. 106 */ TestStatusEntity( String testName, long timestamp, int passCount, int failCount, List<TestCaseReference> failingTestCases)107 public TestStatusEntity( 108 String testName, 109 long timestamp, 110 int passCount, 111 int failCount, 112 List<TestCaseReference> failingTestCases) { 113 this.testName = testName; 114 this.updatedTimestamp = timestamp; 115 this.passCount = passCount; 116 this.failCount = failCount; 117 this.failingTestCases = failingTestCases; 118 } 119 120 /** 121 * Create a TestEntity object without metadata. 122 * 123 * @param testName The name of the test. 124 */ TestStatusEntity(String testName)125 public TestStatusEntity(String testName) { 126 this(testName, 0, -1, -1, new ArrayList<TestCaseReference>()); 127 } 128 129 /** Saving function for the instance of this class */ 130 @Override save()131 public com.googlecode.objectify.Key<TestStatusEntity> save() { 132 return ofy().save().entity(this).now(); 133 } 134 toEntity()135 public Entity toEntity() { 136 Entity testEntity = new Entity(KIND, this.testName); 137 if (this.updatedTimestamp >= 0 && this.passCount >= 0 && this.failCount >= 0) { 138 testEntity.setProperty(UPDATED_TIMESTAMP, this.updatedTimestamp); 139 testEntity.setProperty(PASS_COUNT, this.passCount); 140 testEntity.setProperty(FAIL_COUNT, this.failCount); 141 if (this.failingTestCases.size() > 0) { 142 List<Long> failingTestcaseIds = new ArrayList<>(); 143 List<Integer> failingTestcaseOffsets = new ArrayList<>(); 144 for (TestCaseReference testCase : this.failingTestCases) { 145 failingTestcaseIds.add(testCase.parentId); 146 failingTestcaseOffsets.add(testCase.offset); 147 } 148 testEntity.setUnindexedProperty(FAILING_IDS, failingTestcaseIds); 149 testEntity.setUnindexedProperty(FAILING_OFFSETS, failingTestcaseOffsets); 150 } 151 } 152 return testEntity; 153 } 154 155 /** 156 * Convert an Entity object to a TestEntity. 157 * 158 * @param e The entity to process. 159 * @return TestEntity object with the properties from e processed, or null if incompatible. 160 */ 161 @SuppressWarnings("unchecked") fromEntity(Entity e)162 public static TestStatusEntity fromEntity(Entity e) { 163 if (!e.getKind().equals(KIND) || e.getKey().getName() == null) { 164 logger.log(Level.WARNING, "Missing test attributes in entity: " + e.toString()); 165 return null; 166 } 167 String testName = e.getKey().getName(); 168 long timestamp = 0; 169 int passCount = -1; 170 int failCount = -1; 171 List<TestCaseReference> failingTestCases = new ArrayList<>(); 172 try { 173 if (e.hasProperty(UPDATED_TIMESTAMP)) { 174 timestamp = (long) e.getProperty(UPDATED_TIMESTAMP); 175 } 176 if (e.hasProperty(PASS_COUNT)) { 177 passCount = ((Long) e.getProperty(PASS_COUNT)).intValue(); 178 } 179 if (e.hasProperty(FAIL_COUNT)) { 180 failCount = ((Long) e.getProperty(FAIL_COUNT)).intValue(); 181 } 182 if (e.hasProperty(FAILING_IDS) && e.hasProperty(FAILING_OFFSETS)) { 183 List<Long> ids = (List<Long>) e.getProperty(FAILING_IDS); 184 List<Long> offsets = (List<Long>) e.getProperty(FAILING_OFFSETS); 185 if (ids.size() == offsets.size()) { 186 for (int i = 0; i < ids.size(); i++) { 187 failingTestCases.add( 188 new TestCaseReference(ids.get(i), offsets.get(i).intValue())); 189 } 190 } 191 } 192 } catch (ClassCastException exception) { 193 // Invalid contents or null values 194 logger.log(Level.WARNING, "Error parsing test entity.", exception); 195 } 196 return new TestStatusEntity(testName, timestamp, passCount, failCount, failingTestCases); 197 } 198 } 199