• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may 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 implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package vogar;
18 
19 import com.google.common.base.Joiner;
20 import com.google.common.base.Splitter;
21 import com.google.common.collect.Iterables;
22 import com.google.gson.stream.JsonReader;
23 import java.io.File;
24 import java.io.FileReader;
25 import java.io.IOException;
26 import java.util.EnumSet;
27 import java.util.EnumMap;
28 import java.util.LinkedHashMap;
29 import java.util.LinkedHashSet;
30 import java.util.Map;
31 import java.util.Set;
32 import java.util.regex.Pattern;
33 
34 /**
35  * A database of expected outcomes. Entries in this database come in two forms.
36  * <ul>
37  *   <li>Outcome expectations name an outcome (or its prefix, such as
38  *       "java.util"), its expected result, and an optional pattern to match
39  *       the expected output.
40  *   <li>Failure expectations include a pattern that may match the output of any
41  *       outcome. These expectations are useful for hiding failures caused by
42  *       cross-cutting features that aren't supported.
43  * </ul>
44  *
45  * <p>If an outcome matches both an outcome expectation and a failure
46  * expectation, the outcome expectation will be returned.
47  */
48 final class ExpectationStore {
49     private static final int PATTERN_FLAGS = Pattern.MULTILINE | Pattern.DOTALL;
50 
51     private final Log log;
52     private final Map<String, Expectation> outcomes = new LinkedHashMap<String, Expectation>();
53     private final Map<String, Expectation> failures = new LinkedHashMap<String, Expectation>();
54 
ExpectationStore(Log log)55     private ExpectationStore(Log log) {
56         this.log = log;
57     }
58 
59     /**
60      * Finds the expected result for the specified action or outcome name. This
61      * returns a value for all names, even if no explicit expectation was set.
62      */
get(String name)63     public Expectation get(String name) {
64         Expectation byName = getByNameOrPackage(name);
65         return byName != null ? byName : Expectation.SUCCESS;
66     }
67 
68     /**
69      * Finds the expected result for the specified outcome after it has
70      * completed. Unlike {@code get()}, this also takes into account the
71      * outcome's output.
72      *
73      * <p>For outcomes that have both a name match and an output match,
74      * exact name matches are preferred, then output matches, then inexact
75      * name matches.
76      */
get(Outcome outcome)77     public Expectation get(Outcome outcome) {
78         Expectation exactNameMatch = outcomes.get(outcome.getName());
79         if (exactNameMatch != null) {
80             return exactNameMatch;
81         }
82 
83         for (Map.Entry<String, Expectation> entry : failures.entrySet()) {
84             if (entry.getValue().matches(outcome)) {
85                 return entry.getValue();
86             }
87         }
88 
89         Expectation byName = getByNameOrPackage(outcome.getName());
90         return byName != null ? byName : Expectation.SUCCESS;
91     }
92 
getByNameOrPackage(String name)93     private Expectation getByNameOrPackage(String name) {
94         while (true) {
95             Expectation expectation = outcomes.get(name);
96             if (expectation != null) {
97                 return expectation;
98             }
99 
100             int dotOrHash = Math.max(name.lastIndexOf('.'), name.lastIndexOf('#'));
101             if (dotOrHash == -1) {
102                 return null;
103             }
104 
105             name = name.substring(0, dotOrHash);
106         }
107     }
108 
parse(Log log, Set<File> expectationFiles, ModeId mode, Variant variant)109     public static ExpectationStore parse(Log log,
110                                          Set<File> expectationFiles,
111                                          ModeId mode,
112                                          Variant variant)
113             throws IOException {
114         ExpectationStore result = new ExpectationStore(log);
115         for (File f : expectationFiles) {
116             if (f.exists()) {
117                 result.parse(f, mode, variant);
118             }
119         }
120         return result;
121     }
122 
parse(File expectationsFile, ModeId mode, Variant variant)123     public void parse(File expectationsFile, ModeId mode, Variant variant) throws IOException {
124         log.verbose("loading expectations file " + expectationsFile);
125 
126         int count = 0;
127         JsonReader reader = null;
128         try {
129             reader = new JsonReader(new FileReader(expectationsFile));
130             reader.setLenient(true);
131             reader.beginArray();
132             while (reader.hasNext()) {
133                 readExpectation(reader, mode, variant);
134                 count++;
135             }
136             reader.endArray();
137 
138             log.verbose("loaded " + count + " expectations from " + expectationsFile);
139         } finally {
140             if (reader != null) {
141                 reader.close();
142             }
143         }
144     }
145 
readExpectation(JsonReader reader, ModeId mode, Variant variant)146     private void readExpectation(JsonReader reader, ModeId mode, Variant variant)
147           throws IOException {
148         boolean isFailure = false;
149         Result result = Result.SUCCESS;
150         Pattern pattern = Expectation.MATCH_ALL_PATTERN;
151         Set<String> names = new LinkedHashSet<String>();
152         Set<String> tags = new LinkedHashSet<String>();
153         Map<ModeId, Set<Variant>> modeVariants = null;
154         Set<ModeId> modes = null;
155         String description = "";
156         long buganizerBug = -1;
157 
158         reader.beginObject();
159         while (reader.hasNext()) {
160             String name = reader.nextName();
161             if (name.equals("result")) {
162                 result = Result.valueOf(reader.nextString());
163             } else if (name.equals("name")) {
164                 names.add(reader.nextString());
165             } else if (name.equals("names")) {
166                 readStrings(reader, names);
167             } else if (name.equals("failure")) {
168                 isFailure = true;
169                 names.add(reader.nextString());
170             } else if (name.equals("pattern")) {
171                 pattern = Pattern.compile(reader.nextString(), PATTERN_FLAGS);
172             } else if (name.equals("substring")) {
173                 pattern = Pattern.compile(
174                         ".*" + Pattern.quote(reader.nextString()) + ".*", PATTERN_FLAGS);
175             } else if (name.equals("tags")) {
176                 readStrings(reader, tags);
177             } else if (name.equals("description")) {
178                 Iterable<String> split = Splitter.on("\n").omitEmptyStrings().trimResults()
179                         .split(reader.nextString());
180                 description = Joiner.on("\n").join(split);
181             } else if (name.equals("bug")) {
182                 buganizerBug = reader.nextLong();
183             } else if (name.equals("modes")) {
184                 modes = readModes(reader);
185             } else if (name.equals("modes_variants")) {
186                 modeVariants = readModesAndVariants(reader);
187             } else {
188                 log.warn("Unhandled name in expectations file: " + name);
189                 reader.skipValue();
190             }
191         }
192         reader.endObject();
193 
194         if (names.isEmpty()) {
195             throw new IllegalArgumentException("Missing 'name' or 'failure' key in " + reader);
196         }
197         if (modes != null && !modes.contains(mode)) {
198             return;
199         }
200         if (modeVariants != null) {
201             Set<Variant> variants = modeVariants.get(mode);
202             if (variants == null || !variants.contains(variant)) {
203                 return;
204             }
205         }
206 
207         Expectation expectation =
208               new Expectation(result, pattern, tags, description, buganizerBug, true);
209         Map<String, Expectation> map = isFailure ? failures : outcomes;
210         for (String name : names) {
211             if (map.put(name, expectation) != null) {
212                 throw new IllegalArgumentException("Duplicate expectations for " + name);
213             }
214         }
215     }
216 
readStrings(JsonReader reader, Set<String> output)217     private void readStrings(JsonReader reader, Set<String> output) throws IOException {
218         reader.beginArray();
219         while (reader.hasNext()) {
220             output.add(reader.nextString());
221         }
222         reader.endArray();
223     }
224 
readModes(JsonReader reader)225     private Set<ModeId> readModes(JsonReader reader) throws IOException {
226         Set<ModeId> result = EnumSet.noneOf(ModeId.class);
227         reader.beginArray();
228         while (reader.hasNext()) {
229             result.add(ModeId.valueOf(reader.nextString().toUpperCase()));
230         }
231         reader.endArray();
232         return result;
233     }
234 
235     /**
236      * Expected format: mode_variants: [["host", "X32"], ["host", "X64"]]
237      */
readModesAndVariants(JsonReader reader)238     private Map<ModeId, Set<Variant>> readModesAndVariants(JsonReader reader) throws IOException {
239         Map<ModeId, Set<Variant>> result = new EnumMap<ModeId, Set<Variant>>(ModeId.class);
240         reader.beginArray();
241         while (reader.hasNext()) {
242             reader.beginArray();
243             ModeId mode = ModeId.valueOf(reader.nextString().toUpperCase());
244             Set<Variant> set = result.get(mode);
245             if (set == null) {
246                 set = EnumSet.noneOf(Variant.class);
247                 result.put(mode, set);
248             }
249             set.add(Variant.valueOf(reader.nextString().toUpperCase()));
250             // Note that the following checks that we are at the end of the array.
251             reader.endArray();
252         }
253         reader.endArray();
254         return result;
255     }
256 
257     /**
258      * Sets the bugIsOpen status on all expectations by querying an external bug
259      * tracker.
260      */
loadBugStatuses(BugDatabase bugDatabase)261     public void loadBugStatuses(BugDatabase bugDatabase) {
262         Iterable<Expectation> allExpectations
263                 = Iterables.concat(outcomes.values(), failures.values());
264 
265         // figure out what bug IDs we're interested in
266         Set<Long> bugs = new LinkedHashSet<Long>();
267         for (Expectation expectation : allExpectations) {
268             if (expectation.getBug() != -1) {
269                 bugs.add(expectation.getBug());
270             }
271         }
272         if (bugs.isEmpty()) {
273             return;
274         }
275 
276         Set<Long> openBugs = bugDatabase.bugsToOpenBugs(bugs);
277 
278         log.verbose("tracking " + openBugs.size() + " open bugs: " + openBugs);
279 
280         // update our expectations with that set
281         for (Expectation expectation : allExpectations) {
282             if (openBugs.contains(expectation.getBug())) {
283                 expectation.setBugIsOpen(true);
284             }
285         }
286     }
287 
288     interface BugDatabase {
bugsToOpenBugs(Set<Long> bugs)289         Set<Long> bugsToOpenBugs(Set<Long> bugs);
290     }
291 
292 }
293