• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }