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 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 */ 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 */ 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 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 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 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 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 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 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 */ 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