• 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.LinkedHashMap;
30 import java.util.LinkedHashSet;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Set;
34 import java.util.regex.Pattern;
35 import vogar.commands.Command;
36 import vogar.util.Log;
37 
38 /**
39  * A database of expected outcomes. Entries in this database come in two forms.
40  * <ul>
41  *   <li>Outcome expectations name an outcome (or its prefix, such as
42  *       "java.util"), its expected result, and an optional pattern to match
43  *       the expected output.
44  *   <li>Failure expectations include a pattern that may match the output of any
45  *       outcome. These expectations are useful for hiding failures caused by
46  *       cross-cutting features that aren't supported.
47  * </ul>
48  *
49  * <p>If an outcome matches both an outcome expectation and a failure
50  * expectation, the outcome expectation will be returned.
51  */
52 public final class ExpectationStore {
53     private static final int PATTERN_FLAGS = Pattern.MULTILINE | Pattern.DOTALL;
54     private final Map<String, Expectation> outcomes = new LinkedHashMap<String, Expectation>();
55     private final Map<String, Expectation> failures = new LinkedHashMap<String, Expectation>();
56 
ExpectationStore()57     private ExpectationStore() {}
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(Set<File> expectationFiles, ModeId mode)109     public static ExpectationStore parse(Set<File> expectationFiles, ModeId mode) throws IOException {
110         ExpectationStore result = new ExpectationStore();
111         for (File f : expectationFiles) {
112             if (f.exists()) {
113                 result.parse(f, mode);
114             }
115         }
116         return result;
117     }
118 
parse(File expectationsFile, ModeId mode)119     public void parse(File expectationsFile, ModeId mode) throws IOException {
120         Log.verbose("loading expectations file " + expectationsFile);
121 
122         int count = 0;
123         JsonReader reader = null;
124         try {
125             reader = new JsonReader(new FileReader(expectationsFile));
126             reader.setLenient(true);
127             reader.beginArray();
128             while (reader.hasNext()) {
129                 readExpectation(reader, mode);
130                 count++;
131             }
132             reader.endArray();
133 
134             Log.verbose("loaded " + count + " expectations from " + expectationsFile);
135         } finally {
136             if (reader != null) {
137                 reader.close();
138             }
139         }
140     }
141 
readExpectation(JsonReader reader, ModeId mode)142     private void readExpectation(JsonReader reader, ModeId mode) throws IOException {
143         boolean isFailure = false;
144         Result result = Result.SUCCESS;
145         Pattern pattern = Expectation.MATCH_ALL_PATTERN;
146         Set<String> names = new LinkedHashSet<String>();
147         Set<String> tags = new LinkedHashSet<String>();
148         Set<ModeId> modes = null;
149         String description = "";
150         long buganizerBug = -1;
151 
152         reader.beginObject();
153         while (reader.hasNext()) {
154             String name = reader.nextName();
155             if (name.equals("result")) {
156                 result = Result.valueOf(reader.nextString());
157             } else if (name.equals("name")) {
158                 names.add(reader.nextString());
159             } else if (name.equals("names")) {
160                 readStrings(reader, names);
161             } else if (name.equals("failure")) {
162                 isFailure = true;
163                 names.add(reader.nextString());
164             } else if (name.equals("pattern")) {
165                 pattern = Pattern.compile(reader.nextString(), PATTERN_FLAGS);
166             } else if (name.equals("substring")) {
167                 pattern = Pattern.compile(".*" + Pattern.quote(reader.nextString()) + ".*", PATTERN_FLAGS);
168             } else if (name.equals("tags")) {
169                 readStrings(reader, tags);
170             } else if (name.equals("description")) {
171                 Iterable<String> split = Splitter.on("\n").omitEmptyStrings().trimResults().split(reader.nextString());
172                 description = Joiner.on("\n").join(split);
173             } else if (name.equals("bug")) {
174                 buganizerBug = reader.nextLong();
175             } else if (name.equals("modes")) {
176                 modes = readModes(reader);
177             } else {
178                 Log.warn("Unhandled name in expectations file: " + name);
179                 reader.skipValue();
180             }
181         }
182         reader.endObject();
183 
184         if (names.isEmpty()) {
185             throw new IllegalArgumentException("Missing 'name' or 'failure' key in " + reader);
186         }
187         if (modes != null && !modes.contains(mode)) {
188             return;
189         }
190 
191         Expectation expectation = new Expectation(result, pattern, tags, description, buganizerBug);
192         Map<String, Expectation> map = isFailure ? failures : outcomes;
193         for (String name : names) {
194             if (map.put(name, expectation) != null) {
195                 throw new IllegalArgumentException("Duplicate expectations for " + name);
196             }
197         }
198     }
199 
readStrings(JsonReader reader, Set<String> output)200     private void readStrings(JsonReader reader, Set<String> output) throws IOException {
201         reader.beginArray();
202         while (reader.hasNext()) {
203             output.add(reader.nextString());
204         }
205         reader.endArray();
206     }
207 
readModes(JsonReader reader)208     private Set<ModeId> readModes(JsonReader reader) throws IOException {
209         Set<ModeId> result = new LinkedHashSet<ModeId>();
210         reader.beginArray();
211         while (reader.hasNext()) {
212             result.add(ModeId.valueOf(reader.nextString().toUpperCase()));
213         }
214         reader.endArray();
215         return result;
216     }
217 
218     /**
219      * Sets the bugIsOpen status on all expectations by querying an external bug
220      * tracker.
221      */
loadBugStatuses(String openBugsCommand)222     public void loadBugStatuses(String openBugsCommand) {
223         Iterable<Expectation> allExpectations = Iterables.concat(outcomes.values(), failures.values());
224 
225         // figure out what bug IDs we're interested in
226         Set<String> bugs = new LinkedHashSet<String>();
227         for (Expectation expectation : allExpectations) {
228             if (expectation.getBug() != -1) {
229                 bugs.add(Long.toString(expectation.getBug()));
230             }
231         }
232         if (bugs.isEmpty()) {
233             return;
234         }
235 
236         // query the external app for open bugs
237         List<String> openBugs = new Command.Builder()
238                 .args(openBugsCommand)
239                 .args(bugs)
240                 .execute();
241         Set<Long> openBugsSet = new LinkedHashSet<Long>();
242         for (String bug : openBugs) {
243             openBugsSet.add(Long.parseLong(bug));
244         }
245 
246         Log.verbose("tracking " + openBugsSet.size() + " open bugs: " + openBugs);
247 
248         // update our expectations with that set
249         for (Expectation expectation : allExpectations) {
250             if (openBugsSet.contains(expectation.getBug())) {
251                 expectation.setBugIsOpen(true);
252             }
253         }
254     }
255 }
256