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 static com.googlecode.objectify.ObjectifyService.ofy;
20 
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.appengine.api.taskqueue.Queue;
25 import com.google.appengine.api.taskqueue.QueueFactory;
26 import com.google.appengine.api.taskqueue.TaskOptions;
27 import com.google.gson.JsonObject;
28 import com.google.gson.JsonPrimitive;
29 import com.googlecode.objectify.annotation.Cache;
30 import com.googlecode.objectify.annotation.Id;
31 import com.googlecode.objectify.annotation.Ignore;
32 import com.googlecode.objectify.annotation.Index;
33 import com.googlecode.objectify.annotation.Parent;
34 import java.util.Date;
35 import java.util.List;
36 import java.util.Objects;
37 import java.util.logging.Level;
38 import java.util.logging.Logger;
39 import java.util.stream.Collectors;
40 import lombok.Data;
41 import lombok.NoArgsConstructor;
42 
43 @com.googlecode.objectify.annotation.Entity(name = "TestPlanRun")
44 @Cache
45 @Data
46 @NoArgsConstructor
47 /** Entity describing test plan run information. */
48 public class TestPlanRunEntity implements DashboardEntity {
49 
50     protected static final Logger logger = Logger.getLogger(TestPlanRunEntity.class.getName());
51 
52     private final String QUEUE_NAME = "coverageApiQueue";
53 
54     public static final String KIND = "TestPlanRun";
55 
56     private static final String COVERAGE_API_URL = "/api/coverage/api/sum";
57 
58     // Property keys
59     public static final String TEST_PLAN_NAME = "testPlanName";
60     public static final String TYPE = "type";
61     public static final String START_TIMESTAMP = "startTimestamp";
62     public static final String END_TIMESTAMP = "endTimestamp";
63     public static final String TEST_BUILD_ID = "testBuildId";
64     public static final String PASS_COUNT = "passCount";
65     public static final String FAIL_COUNT = "failCount";
66     public static final String TOTAL_API_COUNT = "totalApiCount";
67     public static final String TOTAL_COVERED_API_COUNT = "coveredApiCount";
68     public static final String TEST_RUNS = "testRuns";
69 
70     @Ignore public Key key;
71 
72     @Id private Long id;
73 
74     @Parent private com.googlecode.objectify.Key<TestPlanEntity> parent;
75 
76     @Index private String testPlanName;
77 
78     @Index private long type;
79 
80     @Index private long startTimestamp;
81 
82     @Index private long endTimestamp;
83 
84     @Index private String testBuildId;
85 
86     @Index private long passCount;
87 
88     @Index private long failCount;
89 
90     @Index private long totalApiCount;
91 
92     @Index private long coveredApiCount;
93 
94     @Ignore private List<Key> oldTestRuns;
95 
96     private List<com.googlecode.objectify.Key<TestRunEntity>> testRuns;
97 
98     /** When this record was created or updated */
99     @Index Date updated;
100 
101     /**
102      * Create a TestPlanRunEntity object describing a test plan run.
103      *
104      * @param testPlanKey The key for the parent entity in the database.
105      * @param type The test run type (e.g. presubmit, postsubmit, other)
106      * @param startTimestamp The time in microseconds when the test plan run started.
107      * @param endTimestamp The time in microseconds when the test plan run ended.
108      * @param testBuildId The build ID of the VTS test build.
109      * @param passCount The number of passing test cases in the run.
110      * @param failCount The number of failing test cases in the run.
111      * @param testRuns A list of keys to the TestRunEntity objects for the plan run run.
112      */
TestPlanRunEntity( Key testPlanKey, String testPlanName, long type, long startTimestamp, long endTimestamp, String testBuildId, long passCount, long failCount, long totalApiCount, long coveredApiCount, List<Key> testRuns)113     public TestPlanRunEntity(
114             Key testPlanKey,
115             String testPlanName,
116             long type,
117             long startTimestamp,
118             long endTimestamp,
119             String testBuildId,
120             long passCount,
121             long failCount,
122             long totalApiCount,
123             long coveredApiCount,
124             List<Key> testRuns) {
125         this.id = startTimestamp;
126         this.key = KeyFactory.createKey(testPlanKey, KIND, startTimestamp);
127         this.testPlanName = testPlanName;
128         this.type = type;
129         this.startTimestamp = startTimestamp;
130         this.endTimestamp = endTimestamp;
131         this.testBuildId = testBuildId;
132         this.passCount = passCount;
133         this.failCount = failCount;
134         this.totalApiCount = totalApiCount;
135         this.coveredApiCount = coveredApiCount;
136         this.oldTestRuns = testRuns;
137         this.testRuns =
138                 testRuns.stream()
139                         .map(
140                                 testRun -> {
141                                     com.googlecode.objectify.Key testParentKey =
142                                             com.googlecode.objectify.Key.create(
143                                                     TestEntity.class,
144                                                     testRun.getParent().getName());
145                                     return com.googlecode.objectify.Key.create(
146                                             testParentKey, TestRunEntity.class, testRun.getId());
147                                 })
148                         .collect(Collectors.toList());
149     }
150 
151     /**
152      * Create a TestPlanRunEntity object describing a test plan run.
153      *
154      * @param testPlanKey The key for the parent entity in the database.
155      * @param type The test run type (e.g. presubmit, postsubmit, other)
156      * @param startTimestamp The time in microseconds when the test plan run started.
157      * @param endTimestamp The time in microseconds when the test plan run ended.
158      * @param testBuildId The build ID of the VTS test build.
159      * @param passCount The number of passing test cases in the run.
160      * @param failCount The number of failing test cases in the run.
161      * @param testRuns A list of keys to the TestRunEntity objects for the plan run run.
162      */
TestPlanRunEntity( com.googlecode.objectify.Key<TestPlanEntity> testPlanKey, String testPlanName, long type, long startTimestamp, long endTimestamp, String testBuildId, long passCount, long failCount, long totalApiCount, long coveredApiCount, List<com.googlecode.objectify.Key<TestRunEntity>> testRuns)163     public TestPlanRunEntity(
164             com.googlecode.objectify.Key<TestPlanEntity> testPlanKey,
165             String testPlanName,
166             long type,
167             long startTimestamp,
168             long endTimestamp,
169             String testBuildId,
170             long passCount,
171             long failCount,
172             long totalApiCount,
173             long coveredApiCount,
174             List<com.googlecode.objectify.Key<TestRunEntity>> testRuns) {
175         this.id = startTimestamp;
176         this.parent = testPlanKey;
177         this.testPlanName = testPlanName;
178         this.type = type;
179         this.startTimestamp = startTimestamp;
180         this.endTimestamp = endTimestamp;
181         this.testBuildId = testBuildId;
182         this.passCount = passCount;
183         this.failCount = failCount;
184         this.totalApiCount = totalApiCount;
185         this.coveredApiCount = coveredApiCount;
186         this.testRuns = testRuns;
187     }
188 
toEntity()189     public Entity toEntity() {
190         Entity planRun = new Entity(this.key);
191         planRun.setProperty(TEST_PLAN_NAME, this.testPlanName);
192         planRun.setProperty(TYPE, this.type);
193         planRun.setProperty(START_TIMESTAMP, this.startTimestamp);
194         planRun.setProperty(END_TIMESTAMP, this.endTimestamp);
195         planRun.setProperty(TEST_BUILD_ID, this.testBuildId.toLowerCase());
196         planRun.setProperty(PASS_COUNT, this.passCount);
197         planRun.setProperty(FAIL_COUNT, this.failCount);
198         if (this.oldTestRuns != null && this.oldTestRuns.size() > 0) {
199             planRun.setUnindexedProperty(TEST_RUNS, this.oldTestRuns);
200         }
201         return planRun;
202     }
203 
204     /** Saving function for the instance of this class */
205     @Override
save()206     public com.googlecode.objectify.Key<TestPlanRunEntity> save() {
207         this.updated = new Date();
208         return ofy().save().entity(this).now();
209     }
210 
211     /** Get UrlSafeKey from this class */
getUrlSafeKey()212     public String getUrlSafeKey() {
213         return this.getOfyKey().toUrlSafe();
214     }
215 
216     /** Add a task to calculate the total number of coverage API */
addCoverageApiTask()217     public void addCoverageApiTask() {
218         if (Objects.isNull(this.testRuns)) {
219             logger.log(Level.WARNING, "testRuns is null so adding task to the queue is skipped!");
220         } else {
221             Queue queue = QueueFactory.getQueue(QUEUE_NAME);
222             queue.add(
223                     TaskOptions.Builder.withUrl(COVERAGE_API_URL)
224                             .param("urlSafeKey", this.getUrlSafeKey())
225                             .method(TaskOptions.Method.POST));
226         }
227     }
228 
229     /**
230      * Get key info from appengine based library.
231      *
232      * @param parentKey parent key.
233      */
getOldKey(Key parentKey)234     public Key getOldKey(Key parentKey) {
235         return KeyFactory.createKey(parentKey, KIND, startTimestamp);
236     }
237 
238     /** Get key info from objecitfy library. */
getOfyKey()239     public com.googlecode.objectify.Key getOfyKey() {
240         return com.googlecode.objectify.Key.create(
241                 this.parent, TestPlanRunEntity.class, this.startTimestamp);
242     }
243 
244     /**
245      * Convert an Entity object to a TestPlanRunEntity.
246      *
247      * @param e The entity to process.
248      * @return TestPlanRunEntity object with the properties from e processed, or null if
249      *     incompatible.
250      */
251     @SuppressWarnings("unchecked")
fromEntity(Entity e)252     public static TestPlanRunEntity fromEntity(Entity e) {
253         if (!e.getKind().equals(KIND)
254                 || !e.hasProperty(TEST_PLAN_NAME)
255                 || !e.hasProperty(TYPE)
256                 || !e.hasProperty(START_TIMESTAMP)
257                 || !e.hasProperty(END_TIMESTAMP)
258                 || !e.hasProperty(TEST_BUILD_ID)
259                 || !e.hasProperty(PASS_COUNT)
260                 || !e.hasProperty(FAIL_COUNT)
261                 || !e.hasProperty(TEST_RUNS)) {
262             logger.log(Level.WARNING, "Missing test run attributes in entity: " + e.toString());
263             return null;
264         }
265         try {
266             String testPlanName = (String) e.getProperty(TEST_PLAN_NAME);
267             long type = (long) e.getProperty(TYPE);
268             long startTimestamp = (long) e.getProperty(START_TIMESTAMP);
269             long endTimestamp = (long) e.getProperty(END_TIMESTAMP);
270             String testBuildId = (String) e.getProperty(TEST_BUILD_ID);
271             long passCount = (long) e.getProperty(PASS_COUNT);
272             long failCount = (long) e.getProperty(FAIL_COUNT);
273 
274             long totalApiCount =
275                     e.hasProperty(TOTAL_API_COUNT) ? (long) e.getProperty(TOTAL_API_COUNT) : 0L;
276             long coveredApiCount =
277                     e.hasProperty(TOTAL_COVERED_API_COUNT)
278                             ? (long) e.getProperty(TOTAL_COVERED_API_COUNT)
279                             : 0L;
280             List<Key> oldTestRuns = (List<Key>) e.getProperty(TEST_RUNS);
281             return new TestPlanRunEntity(
282                     e.getKey().getParent(),
283                     testPlanName,
284                     type,
285                     startTimestamp,
286                     endTimestamp,
287                     testBuildId,
288                     passCount,
289                     failCount,
290                     totalApiCount,
291                     coveredApiCount,
292                     oldTestRuns);
293         } catch (ClassCastException exception) {
294             // Invalid cast
295             logger.log(Level.WARNING, "Error parsing test plan run entity.", exception);
296         }
297         return null;
298     }
299 
toJson()300     public JsonObject toJson() {
301         JsonObject json = new JsonObject();
302         json.add(TEST_PLAN_NAME, new JsonPrimitive(this.testPlanName));
303         json.add(TEST_BUILD_ID, new JsonPrimitive(this.testBuildId));
304         json.add(PASS_COUNT, new JsonPrimitive(this.passCount));
305         json.add(FAIL_COUNT, new JsonPrimitive(this.failCount));
306         json.add(START_TIMESTAMP, new JsonPrimitive(this.startTimestamp));
307         json.add(END_TIMESTAMP, new JsonPrimitive(this.endTimestamp));
308         return json;
309     }
310 }
311