• 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.caliper.internal.gson.stream.JsonReader;
20 
21 import com.android.json.stream.JsonReader;
22 import com.google.common.base.Joiner;
23 import com.google.common.base.Splitter;
24 import com.google.common.collect.Iterables;
25 
26 import java.io.File;
27 import java.io.FileReader;
28 import java.io.IOException;
29 import java.util.Collections;
30 import java.util.LinkedHashMap;
31 import java.util.LinkedHashSet;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Set;
35 import java.util.regex.Pattern;
36 import vogar.commands.Command;
37 import vogar.util.Log;
38 
39 /**
40  * A database of expected outcomes. Entries in this database come in two forms.
41  * <ul>
42  *   <li>Outcome expectations name an outcome (or its prefix, such as
43  *       "java.util"), its expected result, and an optional pattern to match
44  *       the expected output.
45  *   <li>Failure expectations include a pattern that may match the output of any
46  *       outcome. These expectations are useful for hiding failures caused by
47  *       cross-cutting features that aren't supported.
48  * </ul>
49  *
50  * <p>If an outcome matches both an outcome expectation and a failure
51  * expectation, the outcome expectation will be returned.
52  */
53 public final class ExpectationStore {
54 
55     /** The pattern to use when no expected output is specified */
56     private static final Pattern MATCH_ALL_PATTERN
57             = Pattern.compile(".*", Pattern.MULTILINE | Pattern.DOTALL);
58 
59     /** The expectation of a general successful run. */
60     private static final Expectation SUCCESS = new Expectation(Result.SUCCESS, MATCH_ALL_PATTERN,
61             Collections.<String>emptySet(), "", -1);
62 
63     private static final int PATTERN_FLAGS = Pattern.MULTILINE | Pattern.DOTALL;
64 
65     private final Map<String, Expectation> outcomes = new LinkedHashMap<String, Expectation>();
66     private final Map<String, Expectation> failures = new LinkedHashMap<String, Expectation>();
67 
ExpectationStore()68     private ExpectationStore() {}
69 
70     /**
71      * Finds the expected result for the specified action or outcome name. This
72      * returns a value for all names, even if no explicit expectation was set.
73      */
get(String name)74     public Expectation get(String name) {
75         Expectation byName = getByNameOrPackage(name);
76         return byName != null ? byName : SUCCESS;
77     }
78 
79     /**
80      * Finds the expected result for the specified outcome after it has
81      * completed. Unlike {@code get()}, this also takes into account the
82      * outcome's output.
83      *
84      * <p>For outcomes that have both a name match and an output match,
85      * exact name matches are preferred, then output matches, then inexact
86      * name matches.
87      */
get(Outcome outcome)88     public Expectation get(Outcome outcome) {
89         Expectation exactNameMatch = outcomes.get(outcome.getName());
90         if (exactNameMatch != null) {
91             return exactNameMatch;
92         }
93 
94         for (Map.Entry<String, Expectation> entry : failures.entrySet()) {
95             if (entry.getValue().matches(outcome)) {
96                 return entry.getValue();
97             }
98         }
99 
100         Expectation byName = getByNameOrPackage(outcome.getName());
101         return byName != null ? byName : SUCCESS;
102     }
103 
getByNameOrPackage(String name)104     private Expectation getByNameOrPackage(String name) {
105         while (true) {
106             Expectation expectation = outcomes.get(name);
107             if (expectation != null) {
108                 return expectation;
109             }
110 
111             int dotOrHash = Math.max(name.lastIndexOf('.'), name.lastIndexOf('#'));
112             if (dotOrHash == -1) {
113                 return null;
114             }
115 
116             name = name.substring(0, dotOrHash);
117         }
118     }
119 
parse(Set<File> expectationFiles, ModeId mode)120     public static ExpectationStore parse(Set<File> expectationFiles, ModeId mode) throws IOException {
121         ExpectationStore result = new ExpectationStore();
122         for (File f : expectationFiles) {
123             if (f.exists()) {
124                 result.parse(f, mode);
125             }
126         }
127         return result;
128     }
129 
parse(File expectationsFile, ModeId mode)130     public void parse(File expectationsFile, ModeId mode) throws IOException {
131         Log.verbose("loading expectations file " + expectationsFile);
132 
133         int count = 0;
134         JsonReader reader = null;
135         try {
136             reader = new JsonReader(new FileReader(expectationsFile));
137             reader.setLenient(true);
138             reader.beginArray();
139             while (reader.hasNext()) {
140                 readExpectation(reader, mode);
141                 count++;
142             }
143             reader.endArray();
144 
145             Log.verbose("loaded " + count + " expectations from " + expectationsFile);
146         } finally {
147             if (reader != null) {
148                 reader.close();
149             }
150         }
151     }
152 
readExpectation(JsonReader reader, ModeId mode)153     private void readExpectation(JsonReader reader, ModeId mode) throws IOException {
154         boolean isFailure = false;
155         Result result = Result.EXEC_FAILED;
156         Pattern pattern = MATCH_ALL_PATTERN;
157         Set<String> names = new LinkedHashSet<String>();
158         Set<String> tags = new LinkedHashSet<String>();
159         Set<ModeId> modes = null;
160         String description = "";
161         long buganizerBug = -1;
162 
163         reader.beginObject();
164         while (reader.hasNext()) {
165             String name = reader.nextName();
166             if (name.equals("result")) {
167                 result = Result.valueOf(reader.nextString());
168             } else if (name.equals("name")) {
169                 names.add(reader.nextString());
170             } else if (name.equals("names")) {
171                 readStrings(reader, names);
172             } else if (name.equals("failure")) {
173                 // isFailure is somewhat arbitrarily keyed on the existence of a "failure"
174                 // element instead of looking at the "result" field. There are only about 5
175                 // expectations in our entire expectation store that have this tag.
176                 //
177                 // TODO: Get rid of it and the "failures" map and just use the outcomes
178                 // map for everything. Both uses seem useless.
179                 isFailure = true;
180                 names.add(reader.nextString());
181             } else if (name.equals("pattern")) {
182                 pattern = Pattern.compile(reader.nextString(), PATTERN_FLAGS);
183             } else if (name.equals("substring")) {
184                 pattern = Pattern.compile(".*" + Pattern.quote(reader.nextString()) + ".*", PATTERN_FLAGS);
185             } else if (name.equals("tags")) {
186                 readStrings(reader, tags);
187             } else if (name.equals("description")) {
188                 Iterable<String> split = Splitter.on("\n").omitEmptyStrings().trimResults().split(reader.nextString());
189                 description = Joiner.on("\n").join(split);
190             } else if (name.equals("bug")) {
191                 buganizerBug = reader.nextLong();
192             } else if (name.equals("modes")) {
193                 modes = readModes(reader);
194             } else {
195                 Log.warn("Unhandled name in expectations file: " + name);
196                 reader.skipValue();
197             }
198         }
199         reader.endObject();
200 
201         if (names.isEmpty()) {
202             throw new IllegalArgumentException("Missing 'name' or 'failure' key in " + reader);
203         }
204         if (modes != null && !modes.contains(mode)) {
205             return;
206         }
207 
208         Expectation expectation = new Expectation(result, pattern, tags, description, buganizerBug);
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 = new LinkedHashSet<ModeId>();
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      * Sets the bugIsOpen status on all expectations by querying an external bug
237      * tracker.
238      */
loadBugStatuses(String openBugsCommand)239     public void loadBugStatuses(String openBugsCommand) {
240         Iterable<Expectation> allExpectations = Iterables.concat(outcomes.values(), failures.values());
241 
242         // figure out what bug IDs we're interested in
243         Set<String> bugs = new LinkedHashSet<String>();
244         for (Expectation expectation : allExpectations) {
245             if (expectation.getBug() != -1) {
246                 bugs.add(Long.toString(expectation.getBug()));
247             }
248         }
249         if (bugs.isEmpty()) {
250             return;
251         }
252 
253         // query the external app for open bugs
254         List<String> openBugs = new Command.Builder()
255                 .args(openBugsCommand)
256                 .args(bugs)
257                 .execute();
258         Set<Long> openBugsSet = new LinkedHashSet<Long>();
259         for (String bug : openBugs) {
260             openBugsSet.add(Long.parseLong(bug));
261         }
262 
263         Log.verbose("tracking " + openBugsSet.size() + " open bugs: " + openBugs);
264 
265         // update our expectations with that set
266         for (Expectation expectation : allExpectations) {
267             if (openBugsSet.contains(expectation.getBug())) {
268                 expectation.setBugIsOpen(true);
269             }
270         }
271     }
272 }
273