• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright 2016 Google Inc. All Rights Reserved.
3  *
4  * <p>Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  * <p>http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * <p>Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11  * express or implied. See the License for the specific language governing permissions and
12  * limitations under the License.
13  */
14 package com.android.vts.util;
15 
16 import com.android.vts.entity.DeviceInfoEntity;
17 import com.android.vts.entity.TestRunEntity;
18 import com.google.appengine.api.datastore.Entity;
19 import com.google.appengine.api.datastore.Key;
20 import com.google.appengine.api.datastore.KeyFactory;
21 import com.google.appengine.api.datastore.Query.CompositeFilterOperator;
22 import com.google.appengine.api.datastore.Query.Filter;
23 import com.google.appengine.api.datastore.Query.FilterOperator;
24 import com.google.appengine.api.datastore.Query.FilterPredicate;
25 import java.util.ArrayList;
26 import java.util.EnumSet;
27 import java.util.HashMap;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.concurrent.TimeUnit;
31 import java.util.regex.Matcher;
32 import java.util.regex.Pattern;
33 
34 /** FilterUtil, a helper class for parsing and matching search queries to data. */
35 public class FilterUtil {
36     private static final String TERM_DELIMITER = ":";
37     private static final String SEARCH_REGEX = "([^\"]\\S*|\".+?\")\\s*";
38 
39     /** Key class to represent a filter token. */
40     public static enum FilterKey {
41         DEVICE_BUILD_ID("devicebuildid", DeviceInfoEntity.BUILD_ID),
42         BRANCH("branch", DeviceInfoEntity.BRANCH),
43         TARGET("target", DeviceInfoEntity.BUILD_FLAVOR),
44         DEVICE("device", DeviceInfoEntity.PRODUCT),
45         VTS_BUILD_ID("vtsbuildid", TestRunEntity.TEST_BUILD_ID),
46         HOSTNAME("hostname", TestRunEntity.HOST_NAME),
47         PASSING("passing", TestRunEntity.PASS_COUNT),
48         NONPASSING("nonpassing", TestRunEntity.FAIL_COUNT);
49 
50         private static final Map<String, FilterKey> keyMap;
51 
52         static {
53             keyMap = new HashMap<>();
54             for (FilterKey k : EnumSet.allOf(FilterKey.class)) {
k.toString()55                 keyMap.put(k.toString(), k);
56             }
57         }
58 
59         /**
60          * Test if a string is a valid key.
61          *
62          * @param keyString The key string.
63          * @return True if they key string matches a key.
64          */
isKey(String keyString)65         public static boolean isKey(String keyString) {
66             return keyMap.containsKey(keyString);
67         }
68 
69         /**
70          * Parses a key string into a key.
71          *
72          * @param keyString The key string.
73          * @return The key matching the key string.
74          */
parse(String keyString)75         public static FilterKey parse(String keyString) {
76             return keyMap.get(keyString);
77         }
78 
79         private final String keyString;
80         private final String property;
81 
82         /**
83          * Constructs a key with the specified key string.
84          *
85          * @param keyString The identifying key string.
86          * @param propertyName The name of the property to match.
87          */
FilterKey(String keyString, String propertyName)88         private FilterKey(String keyString, String propertyName) {
89             this.keyString = keyString;
90             this.property = propertyName;
91         }
92 
93         @Override
toString()94         public String toString() {
95             return this.keyString;
96         }
97 
getFilterForString(String matchString)98         public Filter getFilterForString(String matchString) {
99             return new FilterPredicate(this.property, FilterOperator.EQUAL, matchString);
100         }
101 
getFilterForNumber(long matchNumber)102         public Filter getFilterForNumber(long matchNumber) {
103             return new FilterPredicate(this.property, FilterOperator.EQUAL, matchNumber);
104         }
105     }
106 
107     /**
108      * Gets a device filter from the user search string.
109      *
110      * @param searchString The user search string.
111      * @return A filter to apply to a test run's device entities.
112      */
getDeviceFilter(String searchString)113     public static Filter getDeviceFilter(String searchString) {
114         Filter deviceFilter = null;
115         if (searchString != null) {
116             Matcher m = Pattern.compile(SEARCH_REGEX).matcher(searchString);
117             while (m.find()) {
118                 String term = m.group(1).replace("\"", "");
119                 if (!term.contains(TERM_DELIMITER))
120                     continue;
121                 String[] terms = term.split(TERM_DELIMITER, 2);
122                 if (terms.length != 2 || !FilterKey.isKey(terms[0].toLowerCase()))
123                     continue;
124 
125                 FilterKey key = FilterKey.parse(terms[0].toLowerCase());
126                 switch (key) {
127                     case BRANCH:
128                     case DEVICE:
129                     case DEVICE_BUILD_ID:
130                     case TARGET:
131                         String value = terms[1].toLowerCase();
132                         Filter f = key.getFilterForString(value);
133                         if (deviceFilter == null) {
134                             deviceFilter = f;
135                         } else {
136                             deviceFilter = CompositeFilterOperator.and(deviceFilter, f);
137                         }
138                         break;
139                     default:
140                         break;
141                 }
142             }
143         }
144         return deviceFilter;
145     }
146 
147     /**
148      * Get a filter on the test run type.
149      *
150      * @param showPresubmit True to display presubmit tests.
151      * @param showPostsubmit True to display postsubmit tests.
152      * @param unfiltered True if no filtering should be applied.
153      * @return A filter on the test type.
154      */
getTestTypeFilter( boolean showPresubmit, boolean showPostsubmit, boolean unfiltered)155     public static Filter getTestTypeFilter(
156             boolean showPresubmit, boolean showPostsubmit, boolean unfiltered) {
157         if (unfiltered) {
158             return null;
159         } else if (showPresubmit && !showPostsubmit) {
160             return new FilterPredicate(TestRunEntity.TYPE, FilterOperator.EQUAL,
161                     TestRunEntity.TestRunType.PRESUBMIT.getNumber());
162         } else if (showPostsubmit && !showPresubmit) {
163             return new FilterPredicate(TestRunEntity.TYPE, FilterOperator.EQUAL,
164                     TestRunEntity.TestRunType.POSTSUBMIT.getNumber());
165         } else {
166             List<Integer> types = new ArrayList<>();
167             types.add(TestRunEntity.TestRunType.PRESUBMIT.getNumber());
168             types.add(TestRunEntity.TestRunType.POSTSUBMIT.getNumber());
169             return new FilterPredicate(TestRunEntity.TYPE, FilterOperator.IN, types);
170         }
171     }
172 
173     /**
174      * Get a filter on test runs from a user search string.
175      *
176      * @param searchString The user search string to parse.
177      * @param runTypeFilter An existing filter on test runs, or null.
178      * @return A filter with the values from the user search string.
179      */
getUserTestFilter(String searchString, Filter runTypeFilter)180     public static Filter getUserTestFilter(String searchString, Filter runTypeFilter) {
181         Filter testRunFilter = runTypeFilter;
182         if (searchString != null) {
183             Matcher m = Pattern.compile(SEARCH_REGEX).matcher(searchString);
184             while (m.find()) {
185                 String term = m.group(1).replace("\"", "");
186                 if (!term.contains(TERM_DELIMITER))
187                     continue;
188                 String[] terms = term.split(TERM_DELIMITER, 2);
189                 if (terms.length != 2 || !FilterKey.isKey(terms[0].toLowerCase()))
190                     continue;
191 
192                 FilterKey key = FilterKey.parse(terms[0].toLowerCase());
193                 Filter f = null;
194                 switch (key) {
195                     case NONPASSING:
196                     case PASSING:
197                         String valueString = terms[1];
198                         try {
199                             Long value = Long.parseLong(valueString);
200                             f = key.getFilterForNumber(value);
201                         } catch (NumberFormatException e) {
202                             // invalid number
203                         }
204                         break;
205                     case HOSTNAME:
206                     case VTS_BUILD_ID:
207                         String value = terms[1].toLowerCase();
208                         f = key.getFilterForString(value);
209                         break;
210                     default:
211                         break;
212                 }
213                 if (testRunFilter == null) {
214                     testRunFilter = f;
215                 } else if (f != null) {
216                     testRunFilter = CompositeFilterOperator.and(testRunFilter, f);
217                 }
218             }
219         }
220         return testRunFilter;
221     }
222 
223     /**
224      * Get the time range filter to apply to a query.
225      *
226      * @param testKey The key of the parent TestEntity object.
227      * @param startTime The start time in microseconds, or null if unbounded.
228      * @param endTime The end time in microseconds, or null if unbounded.
229      * @param testRunFilter The existing filter on test runs to apply, or null.
230      * @return A filter to apply on test runs.
231      */
getTimeFilter( Key testKey, Long startTime, Long endTime, Filter testRunFilter)232     public static Filter getTimeFilter(
233             Key testKey, Long startTime, Long endTime, Filter testRunFilter) {
234         if (startTime == null && endTime == null) {
235             endTime = TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis());
236         }
237 
238         Filter startFilter = null;
239         Filter endFilter = null;
240         Filter filter = null;
241         if (startTime != null) {
242             Key startKey = KeyFactory.createKey(testKey, TestRunEntity.KIND, startTime);
243             startFilter = new FilterPredicate(
244                     Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN_OR_EQUAL, startKey);
245             filter = startFilter;
246         }
247         if (endTime != null) {
248             Key endKey = KeyFactory.createKey(testKey, TestRunEntity.KIND, endTime);
249             endFilter = new FilterPredicate(
250                     Entity.KEY_RESERVED_PROPERTY, FilterOperator.LESS_THAN_OR_EQUAL, endKey);
251             filter = endFilter;
252         }
253         if (startFilter != null && endFilter != null) {
254             filter = CompositeFilterOperator.and(startFilter, endFilter);
255         }
256         if (testRunFilter != null) {
257             filter = CompositeFilterOperator.and(filter, testRunFilter);
258         }
259         return filter;
260     }
261 
getTimeFilter(Key testKey, Long startTime, Long endTime)262     public static Filter getTimeFilter(Key testKey, Long startTime, Long endTime) {
263         return getTimeFilter(testKey, startTime, endTime, null);
264     }
265 }
266