Home | History | Annotate | Download | only in vogar
      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 
     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      */
     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(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 
    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 
    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 
    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 
    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      */
    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      */
    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 {
    289         Set<Long> bugsToOpenBugs(Set<Long> bugs);
    290     }
    291 
    292 }
    293