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