blob: 334d20079d15a29a2acabde8433a73fd11c57d39 [file] [log] [blame]
Nikita Iashchenko9f15d942022-06-06 15:56:28 +01001/*
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
17import com.google.gson.stream.JsonReader;
18import java.io.FileNotFoundException;
19import java.io.FileReader;
20import java.io.IOException;
21import java.util.HashSet;
22import java.util.LinkedHashSet;
23import java.util.List;
24import java.util.Set;
25import java.util.regex.Pattern;
26import 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 */
37class 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
53 public static void main(String[] args) {
54 for (String arg : args) {
55 info("Checking " + arg);
56 checkExpectationFile(arg);
57 }
58 }
59
60 private static void info(String message) {
61 System.err.println(message);
62 }
63
64 private static void error(String message) {
65 System.err.println(message);
66 System.exit(1);
67 }
68
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
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
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
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 */
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}