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.android.vts.proto.VtsReportMessage.CoverageReportMessage;
22 import com.google.appengine.api.datastore.Entity;
23 import com.google.appengine.api.datastore.Key;
24 import com.googlecode.objectify.annotation.Cache;
25 import com.googlecode.objectify.annotation.Id;
26 import com.googlecode.objectify.annotation.Ignore;
27 import com.googlecode.objectify.annotation.Index;
28 import com.googlecode.objectify.annotation.Parent;
29 import java.io.UnsupportedEncodingException;
30 import java.net.URLEncoder;
31 import java.util.ArrayList;
32 import java.util.Comparator;
33 import java.util.List;
34 import java.util.Objects;
35 import java.util.Properties;
36 import java.util.logging.Level;
37 import java.util.logging.Logger;
38 import lombok.Data;
39 import lombok.Getter;
40 import lombok.NoArgsConstructor;
41 import lombok.Setter;
42 
43 @com.googlecode.objectify.annotation.Entity(name = "Coverage")
44 @Cache
45 @Data
46 @NoArgsConstructor
47 /** Object describing coverage data gathered for a file. */
48 public class CoverageEntity implements DashboardEntity {
49 
50     protected static final Logger logger = Logger.getLogger(CoverageEntity.class.getName());
51 
52     public static final String KIND = "Coverage";
53 
54     public static String GERRIT_URI;
55 
56     // Property keys
57     public static final String GROUP = "group";
58     public static final String COVERED_LINE_COUNT = "coveredCount";
59     public static final String TOTAL_LINE_COUNT = "totalCount";
60     public static final String FILE_PATH = "filePath";
61     public static final String PROJECT_NAME = "projectName";
62     public static final String PROJECT_VERSION = "projectVersion";
63     public static final String LINE_COVERAGE = "lineCoverage";
64 
65     @Ignore @Getter @Setter private Key parentKey;
66 
67     @Id @Getter @Setter private Long id;
68 
69     @Parent @Getter @Setter private com.googlecode.objectify.Key<?> testParent;
70 
71     @Index @Getter @Setter private String group;
72 
73     @Getter @Setter private long coveredCount;
74 
75     @Getter @Setter private long totalCount;
76 
77     @Index @Getter @Setter private String filePath;
78 
79     @Getter @Setter private String projectName;
80 
81     @Getter @Setter private String projectVersion;
82 
83     @Getter @Setter private List<Long> lineCoverage;
84 
85     /** CoverageEntity isIgnored field */
86     @Index @Getter @Setter Boolean isIgnored;
87 
88     /**
89      * Create a CoverageEntity object for a file.
90      *
91      * @param parentKey The key to the parent TestRunEntity object in the database.
92      * @param group The group within the test run describing the coverage.
93      * @param coveredLineCount The total number of covered lines in the file.
94      * @param totalLineCount The total number of uncovered executable lines in the file.
95      * @param filePath The path to the file.
96      * @param projectName The name of the git project.
97      * @param projectVersion The commit hash of the project at the time the test was executed.
98      * @param lineCoverage List of coverage counts per executable line in the file.
99      */
CoverageEntity( Key parentKey, String group, long coveredLineCount, long totalLineCount, String filePath, String projectName, String projectVersion, List<Long> lineCoverage)100     public CoverageEntity(
101             Key parentKey,
102             String group,
103             long coveredLineCount,
104             long totalLineCount,
105             String filePath,
106             String projectName,
107             String projectVersion,
108             List<Long> lineCoverage) {
109         this.parentKey = parentKey;
110         this.group = group;
111         this.coveredCount = coveredLineCount;
112         this.totalCount = totalLineCount;
113         this.filePath = filePath;
114         this.projectName = projectName;
115         this.projectVersion = projectVersion;
116         this.lineCoverage = lineCoverage;
117     }
118 
119     /**
120      * Create a CoverageEntity object for a file.
121      *
122      * @param testParent The objectify key to the parent TestRunEntity object in the database.
123      * @param group The group within the test run describing the coverage.
124      * @param coveredLineCount The total number of covered lines in the file.
125      * @param totalLineCount The total number of uncovered executable lines in the file.
126      * @param filePath The path to the file.
127      * @param projectName The name of the git project.
128      * @param projectVersion The commit hash of the project at the time the test was executed.
129      * @param lineCoverage List of coverage counts per executable line in the file.
130      */
CoverageEntity( com.googlecode.objectify.Key testParent, String group, long coveredLineCount, long totalLineCount, String filePath, String projectName, String projectVersion, List<Long> lineCoverage)131     public CoverageEntity(
132             com.googlecode.objectify.Key testParent,
133             String group,
134             long coveredLineCount,
135             long totalLineCount,
136             String filePath,
137             String projectName,
138             String projectVersion,
139             List<Long> lineCoverage) {
140         this.testParent = testParent;
141         this.group = group;
142         this.coveredCount = coveredLineCount;
143         this.totalCount = totalLineCount;
144         this.filePath = filePath;
145         this.projectName = projectName;
146         this.projectVersion = projectVersion;
147         this.lineCoverage = lineCoverage;
148     }
149 
150     /** find coverage entity by ID */
findById(String testName, String testRunId, String id)151     public static CoverageEntity findById(String testName, String testRunId, String id) {
152         com.googlecode.objectify.Key testKey =
153                 com.googlecode.objectify.Key.create(TestEntity.class, testName);
154         com.googlecode.objectify.Key testRunKey =
155                 com.googlecode.objectify.Key.create(
156                         testKey, TestRunEntity.class, Long.parseLong(testRunId));
157         return ofy().load()
158                 .type(CoverageEntity.class)
159                 .parent(testRunKey)
160                 .id(Long.parseLong(id))
161                 .now();
162     }
163 
setPropertyValues(Properties newSystemConfigProp)164     public static void setPropertyValues(Properties newSystemConfigProp) {
165         GERRIT_URI = newSystemConfigProp.getProperty("gerrit.uri");
166     }
167 
168     /** Saving function for the instance of this class */
169     @Override
save()170     public com.googlecode.objectify.Key<CoverageEntity> save() {
171         return ofy().save().entity(this).now();
172     }
173 
174     /** Get percentage from calculating coveredCount and totalCount values */
getPercentage()175     public Double getPercentage() {
176         return Math.round(coveredCount * 10000d / totalCount) / 100d;
177     }
178 
179     /** Get Gerrit Url function from the attributes of this class */
getGerritUrl()180     public String getGerritUrl() throws UnsupportedEncodingException {
181         String gerritPath =
182                 GERRIT_URI
183                         + "/projects/"
184                         + URLEncoder.encode(projectName, "UTF-8")
185                         + "/commits/"
186                         + URLEncoder.encode(projectVersion, "UTF-8")
187                         + "/files/"
188                         + URLEncoder.encode(filePath, "UTF-8")
189                         + "/content";
190         return gerritPath;
191     }
192 
193     /* Comparator for sorting the list by isIgnored field */
194     public static Comparator<CoverageEntity> isIgnoredComparator =
195             new Comparator<CoverageEntity>() {
196 
197                 public int compare(CoverageEntity coverageEntity1, CoverageEntity coverageEntity2) {
198                     Boolean isIgnored1 =
199                             Objects.isNull(coverageEntity1.getIsIgnored())
200                                     ? false
201                                     : coverageEntity1.getIsIgnored();
202                     Boolean isIgnored2 =
203                             Objects.isNull(coverageEntity2.getIsIgnored())
204                                     ? false
205                                     : coverageEntity2.getIsIgnored();
206 
207                     // ascending order
208                     return isIgnored1.compareTo(isIgnored2);
209                 }
210             };
211 
toEntity()212     public Entity toEntity() {
213         Entity coverageEntity = new Entity(KIND, parentKey);
214         coverageEntity.setProperty(GROUP, group);
215         coverageEntity.setUnindexedProperty(COVERED_LINE_COUNT, coveredCount);
216         coverageEntity.setUnindexedProperty(TOTAL_LINE_COUNT, totalCount);
217         coverageEntity.setProperty(FILE_PATH, filePath);
218         coverageEntity.setUnindexedProperty(PROJECT_NAME, projectName);
219         coverageEntity.setUnindexedProperty(PROJECT_VERSION, projectVersion);
220         if (lineCoverage != null && lineCoverage.size() > 0) {
221             coverageEntity.setUnindexedProperty(LINE_COVERAGE, lineCoverage);
222         }
223         return coverageEntity;
224     }
225 
226     /**
227      * Convert an Entity object to a CoverageEntity.
228      *
229      * @param e The entity to process.
230      * @return CoverageEntity object with the properties from e, or null if incompatible.
231      */
232     @SuppressWarnings("unchecked")
fromEntity(Entity e)233     public static CoverageEntity fromEntity(Entity e) {
234         if (!e.getKind().equals(KIND)
235                 || !e.hasProperty(GROUP)
236                 || !e.hasProperty(COVERED_LINE_COUNT)
237                 || !e.hasProperty(TOTAL_LINE_COUNT)
238                 || !e.hasProperty(FILE_PATH)
239                 || !e.hasProperty(PROJECT_NAME)
240                 || !e.hasProperty(PROJECT_VERSION)) {
241             logger.log(Level.WARNING, "Missing coverage attributes in entity: " + e.toString());
242             return null;
243         }
244         try {
245             String group = (String) e.getProperty(GROUP);
246             long coveredLineCount = (long) e.getProperty(COVERED_LINE_COUNT);
247             long totalLineCount = (long) e.getProperty(TOTAL_LINE_COUNT);
248             String filePath = (String) e.getProperty(FILE_PATH);
249             String projectName = (String) e.getProperty(PROJECT_NAME);
250             String projectVersion = (String) e.getProperty(PROJECT_VERSION);
251             List<Long> lineCoverage;
252             if (e.hasProperty(LINE_COVERAGE)) {
253                 lineCoverage = (List<Long>) e.getProperty(LINE_COVERAGE);
254             } else {
255                 lineCoverage = new ArrayList<>();
256             }
257             return new CoverageEntity(
258                     e.getKey().getParent(),
259                     group,
260                     coveredLineCount,
261                     totalLineCount,
262                     filePath,
263                     projectName,
264                     projectVersion,
265                     lineCoverage);
266         } catch (ClassCastException exception) {
267             // Invalid contents or null values
268             logger.log(Level.WARNING, "Error parsing coverage entity.", exception);
269         }
270         return null;
271     }
272 
273     /**
274      * Convert a coverage report to a CoverageEntity.
275      *
276      * @param parentKey The ancestor key for the coverage entity.
277      * @param group The group to display the coverage report with.
278      * @param coverage The coverage report containing coverage data.
279      * @return The CoverageEntity for the coverage report message, or null if not compatible.
280      */
fromCoverageReport( com.googlecode.objectify.Key parentKey, String group, CoverageReportMessage coverage)281     public static CoverageEntity fromCoverageReport(
282             com.googlecode.objectify.Key parentKey, String group, CoverageReportMessage coverage) {
283         if (!coverage.hasFilePath()
284                 || !coverage.hasProjectName()
285                 || !coverage.hasRevision()
286                 || !coverage.hasTotalLineCount()
287                 || !coverage.hasCoveredLineCount()) {
288             return null; // invalid coverage report;
289         }
290         long coveredLineCount = coverage.getCoveredLineCount();
291         long totalLineCount = coverage.getTotalLineCount();
292         String filePath = coverage.getFilePath().toStringUtf8();
293         String projectName = coverage.getProjectName().toStringUtf8();
294         String projectVersion = coverage.getRevision().toStringUtf8();
295         List<Long> lineCoverage = null;
296         if (coverage.getLineCoverageVectorCount() > 0) {
297             lineCoverage = new ArrayList<>();
298             for (long count : coverage.getLineCoverageVectorList()) {
299                 lineCoverage.add(count);
300             }
301         }
302         return new CoverageEntity(
303                 parentKey,
304                 group,
305                 coveredLineCount,
306                 totalLineCount,
307                 filePath,
308                 projectName,
309                 projectVersion,
310                 lineCoverage);
311     }
312 }
313