1 /*
2  * Copyright (C) 2018 The Android Open Source Project
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.google.common.base.Strings;
20 import com.googlecode.objectify.Key;
21 import com.googlecode.objectify.annotation.Cache;
22 import com.googlecode.objectify.annotation.Entity;
23 import com.googlecode.objectify.annotation.Id;
24 import com.googlecode.objectify.annotation.Ignore;
25 import com.googlecode.objectify.annotation.Index;
26 import com.googlecode.objectify.annotation.Parent;
27 import java.util.logging.Level;
28 import java.util.logging.Logger;
29 import lombok.EqualsAndHashCode;
30 import lombok.Getter;
31 import lombok.NoArgsConstructor;
32 import lombok.Setter;
33 import org.apache.commons.io.IOUtils;
34 import org.apache.commons.lang.text.StrSubstitutor;
35 import org.apache.http.NameValuePair;
36 import org.apache.http.ParseException;
37 import org.apache.http.client.utils.URIUtils;
38 import org.apache.http.client.utils.URLEncodedUtils;
39 import org.apache.http.message.BasicNameValuePair;
40 
41 import java.io.FileNotFoundException;
42 import java.io.IOException;
43 import java.io.InputStream;
44 import java.math.BigDecimal;
45 import java.net.URI;
46 import java.net.URISyntaxException;
47 import java.nio.charset.StandardCharsets;
48 import java.util.ArrayList;
49 import java.util.Arrays;
50 import java.util.Date;
51 import java.util.HashMap;
52 import java.util.List;
53 import java.util.Map;
54 import java.util.Properties;
55 import java.util.regex.Matcher;
56 import java.util.regex.Pattern;
57 import java.util.stream.Collectors;
58 import java.util.stream.IntStream;
59 import java.util.stream.Stream;
60 
61 import static com.googlecode.objectify.ObjectifyService.ofy;
62 
63 /** Embeded TestType Class for determining testType and search function */
64 @Index
65 @NoArgsConstructor
66 class TestTypeIndex {
67 
68     /** Embeded TOT field, search field name "testTypeIndex.TOT" */
69     private Boolean TOT;
70 
71     /** Embeded OTA field, search field name "testTypeIndex.OTA" */
72     private Boolean OTA;
73 
74     /** Embeded SIGNED field, search field name "testTypeIndex.SIGNED" */
75     private Boolean SIGNED;
76 
77     /** Maximum bit size */
78     @Ignore private int bitSize = 6;
79 
80     @Ignore
81     private List<Integer> totTypeList = this.getTypeList(TestSuiteResultEntity.TestType.TOT.value);
82 
83     @Ignore
84     private List<Integer> otaTypeList = this.getTypeList(TestSuiteResultEntity.TestType.OTA.value);
85 
86     @Ignore
87     private List<Integer> signedTypeList =
88             this.getTypeList(TestSuiteResultEntity.TestType.SIGNED.value);
89 
90     /** Retrieving the list of integers for each category type */
getTypeList(int typeNum)91     private List<Integer> getTypeList(int typeNum) {
92         return IntStream.range(0, (1 << (bitSize - 1)))
93                 .filter(i -> (i & typeNum) > 0)
94                 .boxed()
95                 .collect(Collectors.toList());
96     }
97 
TestTypeIndex(int testType)98     public TestTypeIndex(int testType) {
99         if (totTypeList.contains(testType)) {
100             this.TOT = true;
101         } else {
102             this.TOT = false;
103         }
104 
105         if (otaTypeList.contains(testType)) {
106             this.OTA = true;
107         } else {
108             this.OTA = false;
109         }
110 
111         if (signedTypeList.contains(testType)) {
112             this.SIGNED = true;
113         } else {
114             this.SIGNED = false;
115         }
116     }
117 }
118 
119 /** Entity Class for saving Test Log Summary */
120 @Cache
121 @Entity
122 @EqualsAndHashCode(of = "id")
123 @NoArgsConstructor
124 public class TestSuiteResultEntity implements DashboardEntity {
125 
126     private static final Logger logger = Logger.getLogger(TestSuiteResultEntity.class.getName());
127 
128     /** Bug Tracking System Property class */
129     private static Properties bugTrackingSystemProp = new Properties();
130 
131     /** System Configuration Property class */
132     private static Properties systemConfigProp = new Properties();
133 
134     public enum TestType {
135         UNKNOWN(0),
136         TOT(1),
137         OTA(1 << 1),
138         SIGNED(1 << 2),
139         PRESUBMIT(1 << 3),
140         MANUAL(1 << 5);
141 
142         public int value;
143 
TestType(int value)144         TestType(int value) {
145             this.value = value;
146         }
147 
value()148         public int value() {
149             return value;
150         }
151     }
152 
153     @Parent @Getter Key<TestSuiteFileEntity> testSuiteFileEntityKey;
154 
155     /** Test Suite start time field */
156     @Id @Getter @Setter Long startTime;
157 
158     /** Test Suite end time field */
159     @Getter @Setter Long endTime;
160 
161     /** Test Suite test type field */
162     @Getter @Setter int testType;
163 
164     /** Embeded test type field */
165     @Index @Getter @Setter TestTypeIndex testTypeIndex;
166 
167     /** Test Suite bootup error field */
168     @Getter @Setter Boolean bootSuccess;
169 
170     /** Test Suite result path field */
171     @Getter @Setter String resultPath;
172 
173     /** Test Suite infra log path field */
174     @Getter @Setter String infraLogPath;
175 
176     /** Test Suite device name field */
177     @Index @Getter @Setter String deviceName;
178 
179     /** Test Suite host name field */
180     @Index @Getter @Setter String hostName;
181 
182     /** Test Suite plan field */
183     @Index @Getter @Setter String suitePlan;
184 
185     /** Test Suite version field */
186     @Getter @Setter String suiteVersion;
187 
188     /** Test Suite name field */
189     @Getter @Setter String suiteName;
190 
191     /** Test Suite build number field */
192     @Getter @Setter String suiteBuildNumber;
193 
194     /** Test Suite test finished module count field */
195     @Getter @Setter int modulesDone;
196 
197     /** Test Suite total number of module field */
198     @Getter @Setter int modulesTotal;
199 
200     /** Test Suite branch field */
201     @Index @Getter @Setter String branch;
202 
203     /** Test Suite build target field */
204     @Index @Getter @Setter String target;
205 
206     /** Test Suite build ID field */
207     @Index @Getter @Setter String buildId;
208 
209     /** Test Suite system fingerprint field */
210     @Getter @Setter String buildSystemFingerprint;
211 
212     /** Test Suite vendor fingerprint field */
213     @Getter @Setter String buildVendorFingerprint;
214 
215     /** Test Suite test count for success field */
216     @Index @Getter @Setter int passedTestCaseCount;
217 
218     /** Test Suite test count for failure field */
219     @Index @Getter @Setter int failedTestCaseCount;
220 
221     /** Test Suite ratio of success to find candidate build */
222     @Index @Getter @Setter float passedTestCaseRatio;
223 
224     /** When this record was created or updated */
225     @Index @Getter Date updated;
226 
227     /** Construction function for TestSuiteResultEntity Class */
TestSuiteResultEntity( Key<TestSuiteFileEntity> testSuiteFileEntityKey, Long startTime, Long endTime, int testType, Boolean bootSuccess, String resultPath, String infraLogPath, String hostName, String suitePlan, String suiteVersion, String suiteName, String suiteBuildNumber, int modulesDone, int modulesTotal, String branch, String target, String buildId, String buildSystemFingerprint, String buildVendorFingerprint, int passedTestCaseCount, int failedTestCaseCount)228     public TestSuiteResultEntity(
229             Key<TestSuiteFileEntity> testSuiteFileEntityKey,
230             Long startTime,
231             Long endTime,
232             int testType,
233             Boolean bootSuccess,
234             String resultPath,
235             String infraLogPath,
236             String hostName,
237             String suitePlan,
238             String suiteVersion,
239             String suiteName,
240             String suiteBuildNumber,
241             int modulesDone,
242             int modulesTotal,
243             String branch,
244             String target,
245             String buildId,
246             String buildSystemFingerprint,
247             String buildVendorFingerprint,
248             int passedTestCaseCount,
249             int failedTestCaseCount) {
250         this.testSuiteFileEntityKey = testSuiteFileEntityKey;
251         this.startTime = startTime;
252         this.endTime = endTime;
253         this.bootSuccess = bootSuccess;
254         this.resultPath = resultPath;
255         this.infraLogPath = infraLogPath;
256         this.hostName = hostName;
257         this.suitePlan = suitePlan;
258         this.suiteVersion = suiteVersion;
259         this.suiteName = suiteName;
260         this.suiteBuildNumber = suiteBuildNumber;
261         this.modulesDone = modulesDone;
262         this.modulesTotal = modulesTotal;
263         this.branch = branch;
264         this.target = target;
265         this.buildId = buildId;
266         this.buildSystemFingerprint = buildSystemFingerprint;
267         this.buildVendorFingerprint = buildVendorFingerprint;
268         this.passedTestCaseCount = passedTestCaseCount;
269         this.failedTestCaseCount = failedTestCaseCount;
270 
271         BigDecimal totalTestCaseCount = new BigDecimal(passedTestCaseCount + failedTestCaseCount);
272         if (totalTestCaseCount.intValue() <= 0) {
273             this.passedTestCaseRatio = 0;
274         } else {
275             BigDecimal passedTestCaseCountDecimal = new BigDecimal(passedTestCaseCount);
276             BigDecimal result =
277                     passedTestCaseCountDecimal.divide(
278                             totalTestCaseCount, 10, BigDecimal.ROUND_FLOOR);
279             this.passedTestCaseRatio = result.floatValue() * 100;
280         }
281 
282         if (!this.buildVendorFingerprint.isEmpty()) {
283             this.deviceName = this.getDeviceNameFromVendorFpt();
284         }
285 
286         this.testType = this.getSuiteResultTestType(testType);
287         this.testTypeIndex = new TestTypeIndex(this.testType);
288     }
289 
290     /** Saving function for the instance of this class */
save(TestSuiteFileEntity newTestSuiteFileEntity)291     public void save(TestSuiteFileEntity newTestSuiteFileEntity) {
292         List<String> checkList =
293                 Arrays.asList(
294                         this.hostName,
295                         this.suitePlan,
296                         this.suiteName,
297                         this.suiteBuildNumber,
298                         this.branch,
299                         this.target,
300                         this.buildId);
301         boolean isAllTrue = checkList.stream().allMatch(val -> Strings.isNullOrEmpty(val));
302 
303         if (isAllTrue) {
304             logger.log(Level.WARNING, "There is null or empty string among required fields!");
305         } else {
306             this.updated = new Date();
307             ofy().transact(() -> {
308                 newTestSuiteFileEntity.save();
309                 ofy().save().entity(this).now();
310             });
311         }
312     }
313 
314     /** Saving function for the instance of this class */
315     @Override
save()316     public Key<TestSuiteResultEntity> save() {
317         return ofy().save().entity(this).now();
318     }
319 
setPropertyValues(Properties newSystemConfigProp)320     public static void setPropertyValues(Properties newSystemConfigProp) {
321         systemConfigProp = newSystemConfigProp;
322         bugTrackingSystemProp = getBugTrackingSystemProp(newSystemConfigProp);
323     }
324 
getBugTrackingSystemProp(Properties newSystemConfigProp)325     private static Properties getBugTrackingSystemProp(Properties newSystemConfigProp) {
326         Properties newBugTrackingSystemProp = new Properties();
327         try {
328             String bugTrackingSystem = newSystemConfigProp.getProperty("bug.tracking.system");
329 
330             if (!bugTrackingSystem.isEmpty()) {
331                 InputStream btsInputStream =
332                         TestSuiteResultEntity.class
333                                 .getClassLoader()
334                                 .getResourceAsStream(
335                                         "bug_tracking_system/"
336                                                 + bugTrackingSystem
337                                                 + "/config.properties");
338                 newBugTrackingSystemProp.load(btsInputStream);
339             }
340         } catch (FileNotFoundException e) {
341             e.printStackTrace();
342         } catch (IOException e) {
343             e.printStackTrace();
344         } finally {
345             return newBugTrackingSystemProp;
346         }
347     }
348 
getTestSuitePlans()349     public static List<TestSuiteResultEntity> getTestSuitePlans() {
350         return ofy().load()
351                 .type(TestSuiteResultEntity.class)
352                 .project("suitePlan")
353                 .distinct(true)
354                 .list();
355     }
356 
getBranchDistinctList()357     public static List<TestSuiteResultEntity> getBranchDistinctList() {
358         return ofy().load()
359                 .type(TestSuiteResultEntity.class)
360                 .project("branch")
361                 .distinct(true)
362                 .list();
363     }
364 
getBuildIdDistinctList()365     public static List<TestSuiteResultEntity> getBuildIdDistinctList() {
366         return ofy().load()
367                 .type(TestSuiteResultEntity.class)
368                 .project("buildId")
369                 .distinct(true)
370                 .list();
371     }
372 
getTargetDistinctList()373     public static List<TestSuiteResultEntity> getTargetDistinctList() {
374         return ofy().load()
375                 .type(TestSuiteResultEntity.class)
376                 .project("target")
377                 .distinct(true)
378                 .list();
379     }
380 
getHostNameDistinctList()381     public static List<TestSuiteResultEntity> getHostNameDistinctList() {
382         return ofy().load()
383                 .type(TestSuiteResultEntity.class)
384                 .project("hostName")
385                 .distinct(true)
386                 .list();
387     }
388 
getDeviceNameFromVendorFpt()389     public String getDeviceNameFromVendorFpt() {
390         String deviceName =
391                 Stream.of(this.buildVendorFingerprint.split("/")).skip(1).findFirst().orElse("");
392         return deviceName;
393     }
394 
getScreenResultLogPath()395     public String getScreenResultLogPath() {
396         String gcsBucketName = systemConfigProp.getProperty("gcs.bucketName");
397         return resultPath.replace("gs://" + gcsBucketName + "/", "");
398     }
399 
getScreenInfraLogPath()400     public String getScreenInfraLogPath() {
401         String gcsInfraLogBucketName = systemConfigProp.getProperty("gcs.infraLogBucketName");
402         return infraLogPath.replace("gs://" + gcsInfraLogBucketName + "/", "");
403     }
404 
getNormalizedVersion(String fingerprint)405     private String getNormalizedVersion(String fingerprint) {
406         Map<String, Pattern> partternMap =
407                 new HashMap<String, Pattern>() {
408                     {
409                         put(
410                                 "9",
411                                 Pattern.compile(
412                                         "(:9(\\.\\d\\.\\d|\\.\\d|)|:P\\w*/)",
413                                         Pattern.CASE_INSENSITIVE));
414                         put(
415                                 "8.1",
416                                 Pattern.compile(
417                                         "(:8\\.1\\.\\d\\/|:O\\w+-MR1/)", Pattern.CASE_INSENSITIVE));
418                         put(
419                                 "8",
420                                 Pattern.compile(
421                                         "(:8\\.0\\.\\d\\/|:O\\w*/)", Pattern.CASE_INSENSITIVE));
422                     }
423                 };
424 
425         for (Map.Entry<String, Pattern> entry : partternMap.entrySet()) {
426             Matcher systemMatcher = entry.getValue().matcher(fingerprint);
427             if (systemMatcher.find()) {
428                 return entry.getKey();
429             }
430         }
431         return "unknown-version";
432     }
433 
getSuiteResultTestType(int testType)434     public int getSuiteResultTestType(int testType) {
435         if (testType == TestType.UNKNOWN.value()) {
436             if (this.getNormalizedVersion(this.buildSystemFingerprint)
437                     != this.getNormalizedVersion(this.buildVendorFingerprint)) {
438                 return TestType.OTA.value();
439             } else if (this.buildVendorFingerprint.endsWith("release-keys")) {
440                 return TestType.SIGNED.value();
441             } else {
442                 return TestType.TOT.value();
443             }
444         } else {
445             return testType;
446         }
447     }
448 
getLabInfraIssueDescription()449     private String getLabInfraIssueDescription() throws IOException {
450 
451         String bugTrackingSystem = systemConfigProp.getProperty("bug.tracking.system");
452 
453         String templateName =
454                 bugTrackingSystemProp.getProperty(
455                         bugTrackingSystem + ".labInfraIssue.template.name");
456 
457         InputStream inputStream =
458                 this.getClass()
459                         .getClassLoader()
460                         .getResourceAsStream(
461                                 "bug_tracking_system/" + bugTrackingSystem + "/" + templateName);
462 
463         String templateDescription = IOUtils.toString(inputStream, StandardCharsets.UTF_8.name());
464 
465         Map<String, String> valuesMap = new HashMap<>();
466         valuesMap.put("suiteBuildNumber", suiteBuildNumber);
467         valuesMap.put("buildId", buildId);
468         valuesMap.put("modulesDone", Integer.toString(modulesDone));
469         valuesMap.put("modulesTotal", Integer.toString(modulesTotal));
470         valuesMap.put("hostName", hostName);
471         valuesMap.put("resultPath", resultPath);
472         valuesMap.put("buildVendorFingerprint", buildVendorFingerprint);
473         valuesMap.put("buildSystemFingerprint", buildSystemFingerprint);
474 
475         StrSubstitutor sub = new StrSubstitutor(valuesMap);
476         String resolvedDescription = sub.replace(templateDescription);
477 
478         return resolvedDescription;
479     }
480 
getCrashSecurityDescription()481     private String getCrashSecurityDescription() throws IOException {
482 
483         String bugTrackingSystem = systemConfigProp.getProperty("bug.tracking.system");
484 
485         String templateName =
486                 bugTrackingSystemProp.getProperty(
487                         bugTrackingSystem + ".crashSecurity.template.name");
488 
489         InputStream inputStream =
490                 this.getClass()
491                         .getClassLoader()
492                         .getResourceAsStream(
493                                 "bug_tracking_system/" + bugTrackingSystem + "/" + templateName);
494 
495         String templateDescription = IOUtils.toString(inputStream, StandardCharsets.UTF_8.name());
496 
497         Map<String, String> valuesMap = new HashMap<>();
498         valuesMap.put("suiteBuildNumber", suiteBuildNumber);
499         valuesMap.put("branch", branch);
500         valuesMap.put("target", target);
501         valuesMap.put("deviceName", deviceName);
502         valuesMap.put("buildId", buildId);
503         valuesMap.put("suiteName", suiteName);
504         valuesMap.put("suitePlan", suitePlan);
505         valuesMap.put("hostName", hostName);
506         valuesMap.put("resultPath", resultPath);
507 
508         StrSubstitutor sub = new StrSubstitutor(valuesMap);
509         String resolvedDescription = sub.replace(templateDescription);
510 
511         return resolvedDescription;
512     }
513 
getBuganizerLink()514     public String getBuganizerLink() throws IOException, ParseException, URISyntaxException {
515 
516         String bugTrackingSystem = systemConfigProp.getProperty("bug.tracking.system");
517 
518         List<NameValuePair> qparams = new ArrayList<NameValuePair>();
519         if (!this.bootSuccess || (this.passedTestCaseCount == 0 && this.failedTestCaseCount == 0)) {
520             qparams.add(
521                     new BasicNameValuePair(
522                             "component",
523                             this.bugTrackingSystemProp.getProperty(
524                                     bugTrackingSystem + ".labInfraIssue.component.id")));
525             qparams.add(
526                     new BasicNameValuePair(
527                             "template",
528                             this.bugTrackingSystemProp.getProperty(
529                                     bugTrackingSystem + ".labInfraIssue.template.id")));
530             qparams.add(new BasicNameValuePair("description", this.getLabInfraIssueDescription()));
531         } else {
532             qparams.add(
533                     new BasicNameValuePair(
534                             "component",
535                             this.bugTrackingSystemProp.getProperty(
536                                     bugTrackingSystem + ".crashSecurity.component.id")));
537             qparams.add(
538                     new BasicNameValuePair(
539                             "template",
540                             this.bugTrackingSystemProp.getProperty(
541                                     bugTrackingSystem + ".crashSecurity.template.id")));
542             qparams.add(new BasicNameValuePair("description", this.getCrashSecurityDescription()));
543         }
544 
545         URI uri =
546                 URIUtils.createURI(
547                         this.bugTrackingSystemProp.getProperty(bugTrackingSystem + ".uri.scheme"),
548                         this.bugTrackingSystemProp.getProperty(bugTrackingSystem + ".uri.host"),
549                         -1,
550                         this.bugTrackingSystemProp.getProperty(bugTrackingSystem + ".uri.path"),
551                         URLEncodedUtils.format(qparams, StandardCharsets.UTF_8.name()),
552                         null);
553         return uri.toString();
554     }
555 }
556