1 /* 2 * Copyright (C) 2022 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 import com.google.gson.stream.JsonReader; 18 import java.io.FileNotFoundException; 19 import java.io.FileReader; 20 import java.io.IOException; 21 import java.util.HashSet; 22 import java.util.LinkedHashSet; 23 import java.util.List; 24 import java.util.Set; 25 import java.util.regex.Pattern; 26 import java.util.regex.PatternSyntaxException; 27 28 /** 29 * Pre upload hook that ensures art-buildbot expectation files (files under //art/tools ending with 30 * "_failures.txt", e.g. //art/tools/libcore_failures.txt) are well-formed json files. 31 * 32 * It makes basic validation of the keys but does not cover all the cases. Parser structure is 33 * based on external/vogar/src/vogar/ExpectationStore.java. 34 * 35 * Hook is set up in //art/PREUPLOAD.cfg See also //tools/repohooks/README.md 36 */ 37 class PresubmitJsonLinter { 38 39 private static final int FLAGS = Pattern.MULTILINE | Pattern.DOTALL; 40 private static final Set<String> RESULTS = new HashSet<>(); 41 42 static { 43 RESULTS.addAll(List.of( 44 "UNSUPPORTED", 45 "COMPILE_FAILED", 46 "EXEC_FAILED", 47 "EXEC_TIMEOUT", 48 "ERROR", 49 "SUCCESS" 50 )); 51 } 52 main(String[] args)53 public static void main(String[] args) { 54 for (String arg : args) { 55 info("Checking " + arg); 56 checkExpectationFile(arg); 57 } 58 } 59 info(String message)60 private static void info(String message) { 61 System.err.println(message); 62 } 63 error(String message)64 private static void error(String message) { 65 System.err.println(message); 66 System.exit(1); 67 } 68 checkExpectationFile(String arg)69 private static void checkExpectationFile(String arg) { 70 JsonReader reader; 71 try { 72 reader = new JsonReader(new FileReader(arg)); 73 } catch (FileNotFoundException e) { 74 error("File '" + arg + "' is not found"); 75 return; 76 } 77 reader.setLenient(true); 78 try { 79 reader.beginArray(); 80 while (reader.hasNext()) { 81 readExpectation(reader); 82 } 83 reader.endArray(); 84 } catch (IOException e) { 85 error("Malformed json: " + reader); 86 } 87 } 88 readExpectation(JsonReader reader)89 private static void readExpectation(JsonReader reader) throws IOException { 90 Set<String> names = new LinkedHashSet<String>(); 91 Set<String> tags = new LinkedHashSet<String>(); 92 boolean readResult = false; 93 boolean readDescription = false; 94 95 reader.beginObject(); 96 while (reader.hasNext()) { 97 String name = reader.nextName(); 98 switch (name) { 99 case "result": 100 String result = reader.nextString(); 101 if (!RESULTS.contains(result)) { 102 error("Invalid 'result' value: '" + result + 103 "'. Expected one of " + String.join(", ", RESULTS) + 104 ". " + reader); 105 } 106 readResult = true; 107 break; 108 case "substring": { 109 try { 110 Pattern.compile( 111 ".*" + Pattern.quote(reader.nextString()) + ".*", FLAGS); 112 } catch (PatternSyntaxException e) { 113 error("Malformed 'substring' value: " + reader); 114 } 115 } 116 case "pattern": { 117 try { 118 Pattern.compile(reader.nextString(), FLAGS); 119 } catch (PatternSyntaxException e) { 120 error("Malformed 'pattern' value: " + reader); 121 } 122 break; 123 } 124 case "failure": 125 names.add(reader.nextString()); 126 break; 127 case "description": 128 reader.nextString(); 129 readDescription = true; 130 break; 131 case "name": 132 names.add(reader.nextString()); 133 break; 134 case "names": 135 readStrings(reader, names); 136 break; 137 case "tags": 138 readStrings(reader, tags); 139 break; 140 case "bug": 141 reader.nextLong(); 142 break; 143 case "modes": 144 readModes(reader); 145 break; 146 case "modes_variants": 147 readModesAndVariants(reader); 148 break; 149 default: 150 error("Unknown key '" + name + "' in expectations file"); 151 reader.skipValue(); 152 break; 153 } 154 } 155 reader.endObject(); 156 157 if (names.isEmpty()) { 158 error("Missing 'name' or 'failure' key in " + reader); 159 } 160 if (!readResult) { 161 error("Missing 'result' key in " + reader); 162 } 163 if (!readDescription) { 164 error("Missing 'description' key in " + reader); 165 } 166 } 167 readStrings(JsonReader reader, Set<String> output)168 private static void readStrings(JsonReader reader, Set<String> output) throws IOException { 169 reader.beginArray(); 170 while (reader.hasNext()) { 171 output.add(reader.nextString()); 172 } 173 reader.endArray(); 174 } 175 readModes(JsonReader reader)176 private static void readModes(JsonReader reader) throws IOException { 177 reader.beginArray(); 178 while (reader.hasNext()) { 179 reader.nextString(); 180 } 181 reader.endArray(); 182 } 183 184 /** 185 * Expected format: mode_variants: [["host", "X32"], ["host", "X64"]] 186 */ readModesAndVariants(JsonReader reader)187 private static void readModesAndVariants(JsonReader reader) throws IOException { 188 reader.beginArray(); 189 while (reader.hasNext()) { 190 reader.beginArray(); 191 reader.nextString(); 192 reader.nextString(); 193 reader.endArray(); 194 } 195 reader.endArray(); 196 } 197 }