Skip to content

Commit 1bf5364

Browse files
dnestorogradinac
authored andcommitted
Check conditions, duplicates and empty config files
1 parent 664c029 commit 1bf5364

File tree

3 files changed

+284
-3
lines changed

3 files changed

+284
-3
lines changed

tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-harness.gradle

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ plugins {
1010
}
1111

1212
import groovy.json.JsonOutput
13+
<<<<<<< HEAD
1314
import org.graalvm.internal.tck.DockerTask
15+
=======
16+
import org.graalvm.internal.tck.ConfigFilesChecker
17+
>>>>>>> cbf3f68 (Check conditions, duplicates and empty config files)
1418
import org.graalvm.internal.tck.ScaffoldTask
1519
import org.graalvm.internal.tck.GrypeTask
1620
import org.graalvm.internal.tck.harness.tasks.CheckstyleInvocationTask
@@ -151,7 +155,14 @@ tasks.register("scaffold", ScaffoldTask.class) { task ->
151155
task.finalizedBy("spotlessApply")
152156
}
153157

158+
<<<<<<< HEAD
154159
tasks.register("pullAllowedDockerImages", DockerTask.class) { task ->
155160
task.setDescription("Pull allowed docker images from list.")
156161
task.setGroup(METADATA_GROUP)
157162
}
163+
=======
164+
tasks.register("checkConfigFiles", ConfigFilesChecker.class) { task ->
165+
task.setDescription("Checks content of config files for a new library.")
166+
task.setGroup(METADATA_GROUP)
167+
}
168+
>>>>>>> cbf3f68 (Check conditions, duplicates and empty config files)
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
package org.graalvm.internal.tck;
2+
3+
import groovy.json.JsonSlurper;
4+
import org.gradle.api.DefaultTask;
5+
import org.gradle.api.tasks.TaskAction;
6+
import org.gradle.api.tasks.options.Option;
7+
8+
import java.io.File;
9+
import java.nio.file.Path;
10+
import java.util.*;
11+
import java.util.stream.Collectors;
12+
13+
/**
14+
* Checks content of config files for a new library.
15+
* <p>
16+
* Run with {@code gradle checkConfigFiles --coordinates com.example:library:1.0.0}.
17+
*
18+
* @author David Nestorovic
19+
*/
20+
public class ConfigFilesChecker extends DefaultTask {
21+
22+
private final List<String> EXPECTED_FILES = Arrays.asList("index.json", "reflect-config.json", "resource-config.json", "serialization-config.json",
23+
"jni-config.json", "proxy-config.json", "predefined-classes-config.json");
24+
25+
private final List<String> ILLEGAL_TYPE_VALUES = List.of("java.lang.Object");
26+
27+
private String coordinates;
28+
29+
@Option(option = "coordinates", description = "Coordinates in the form of group:artifact:version")
30+
void setCoordinates(String coordinates) {
31+
this.coordinates = coordinates;
32+
}
33+
34+
35+
@TaskAction
36+
void run() throws IllegalArgumentException {
37+
Coordinates coordinates = Coordinates.parse(this.coordinates);
38+
39+
File coordinatesMetadataRoot = getProject().file(CoordinateUtils.replace("metadata/$group$/$artifact$/$version$", coordinates));
40+
if (!coordinatesMetadataRoot.exists()) {
41+
throw new IllegalArgumentException("ERROR: Cannot find metadata directory for given coordinates: " + this.coordinates);
42+
}
43+
44+
boolean containsErrors = false;
45+
List<File> filesInMetadata = getConfigFilesForMetadataDir(coordinatesMetadataRoot);
46+
for (File file : filesInMetadata) {
47+
if (file.getName().equalsIgnoreCase("reflect-config.json")) {
48+
containsErrors |= reflectConfigFilesContainsErrors(file);
49+
}
50+
51+
if (file.getName().equalsIgnoreCase("resource-config.json")) {
52+
containsErrors |= resourceConfigFilesContainsErrors(file);
53+
}
54+
55+
if (file.getName().equalsIgnoreCase("serialization-config.json")) {
56+
containsErrors |= serializationConfigFilesContainsErrors(file);
57+
}
58+
59+
if (file.getName().equalsIgnoreCase("proxy-config.json")) {
60+
containsErrors |= proxyConfigFilesContainsErrors(file);
61+
}
62+
63+
if (file.getName().equalsIgnoreCase("jni-config.json")) {
64+
containsErrors |= jniConfigFilesContainsErrors(file);
65+
}
66+
}
67+
68+
if (containsErrors) {
69+
throw new IllegalStateException("Errors above found for: " + this.coordinates);
70+
}
71+
}
72+
73+
private List<File> getConfigFilesForMetadataDir(File root) throws RuntimeException {
74+
List<File> files = new ArrayList<>();
75+
File [] content = root.listFiles();
76+
77+
if (content == null) {
78+
throw new RuntimeException("ERROR: Failed to load content of " + root.toURI());
79+
}
80+
81+
Arrays.stream(content).forEach(file -> {
82+
String fileName = file.getName();
83+
84+
if (EXPECTED_FILES.stream().noneMatch(f -> f.equalsIgnoreCase(fileName))) {
85+
throw new IllegalStateException("ERROR: Unexpected file " + file.toURI() + " found in " + root.toURI());
86+
}
87+
88+
files.add(file);
89+
});
90+
91+
return files;
92+
}
93+
94+
@SuppressWarnings("unchecked")
95+
private List<Map<String, Object>> getConfigEntries(File file) {
96+
JsonSlurper js = new JsonSlurper();
97+
return ((List<Object>) js.parse(file)).stream().map(e -> (Map<String, Object>)e).collect(Collectors.toList());
98+
}
99+
100+
@SuppressWarnings("unchecked")
101+
private Map<String, Object> getConfigEntry(File file) {
102+
JsonSlurper js = new JsonSlurper();
103+
return (Map<String, Object>) js.parse(file);
104+
}
105+
106+
private boolean reflectConfigFilesContainsErrors(File file) {
107+
List<Map<String, Object>> entries = getConfigEntries(file);
108+
109+
if (entries.size() == 0) {
110+
System.out.println("ERROR: empty reflect-config detected: " + file.toURI());
111+
return true;
112+
}
113+
114+
boolean containsErrors = containsDuplicatedEntries(entries, file);
115+
for (var entry : entries) {
116+
containsErrors |= checkTypeReachable(entry, file);
117+
}
118+
119+
return containsErrors;
120+
}
121+
122+
@SuppressWarnings("unchecked")
123+
private boolean resourceConfigFilesContainsErrors(File file) {
124+
Map<String, Object> entries = getConfigEntry(file);
125+
List<Map<String, Object>> bundles = (List<Map<String, Object>>) entries.get("bundles");
126+
Map<String, Object> resources = (Map<String, Object>) entries.get("resources");
127+
128+
boolean containsErrors = false;
129+
if (resources != null) {
130+
List<Map<String, Object>> includes = (List<Map<String, Object>>) resources.get("includes");
131+
List<Map<String, Object>> excludes = (List<Map<String, Object>>) resources.get("excludes");
132+
133+
if (listNullOrEmpty(includes) && listNullOrEmpty(excludes) && listNullOrEmpty(bundles)){
134+
System.out.println("ERROR: empty resource-config detected: " + file.toURI());
135+
return true;
136+
}
137+
138+
// check include entries
139+
if (includes != null) {
140+
containsErrors |= containsDuplicatedEntries(includes, file);
141+
142+
for (var entry : includes) {
143+
containsErrors |= checkTypeReachable(entry, file);
144+
}
145+
}
146+
147+
// check exclude entries
148+
if (excludes != null) {
149+
containsErrors |= containsDuplicatedEntries(excludes, file);
150+
151+
for (var entry : excludes) {
152+
containsErrors |= checkTypeReachable(entry, file);
153+
}
154+
}
155+
}
156+
157+
return containsErrors;
158+
}
159+
160+
@SuppressWarnings("unchecked")
161+
private boolean serializationConfigFilesContainsErrors(File file) {
162+
Map<String, Object> entries = getConfigEntry(file);
163+
164+
List<Map<String, Object>> types = (List<Map<String, Object>>) entries.get("types");
165+
List<Map<String, Object>> lambdaCapturingTypes = (List<Map<String, Object>>) entries.get("lambdaCapturingTypes");
166+
167+
if (listNullOrEmpty(types) && listNullOrEmpty(lambdaCapturingTypes)) {
168+
System.out.println("ERROR: empty serialization-config detected: " + file.toURI());
169+
return true;
170+
}
171+
172+
boolean containsErrors = false;
173+
if (types != null) {
174+
// check include entries
175+
containsErrors |= containsDuplicatedEntries(types, file);
176+
177+
for (var entry : types) {
178+
containsErrors |= checkTypeReachable(entry, file);
179+
}
180+
}
181+
182+
if (lambdaCapturingTypes != null) {
183+
// check include entries
184+
containsErrors |= containsDuplicatedEntries(lambdaCapturingTypes, file);
185+
186+
for (var entry : lambdaCapturingTypes) {
187+
containsErrors |= checkTypeReachable(entry, file);
188+
}
189+
}
190+
191+
return containsErrors;
192+
}
193+
194+
private boolean proxyConfigFilesContainsErrors(File file) {
195+
List<Map<String, Object>> entries = getConfigEntries(file);
196+
197+
if (entries.size() == 0) {
198+
System.out.println("ERROR: empty proxy-config detected: " + file.toURI());
199+
return true;
200+
}
201+
202+
boolean containsErrors = containsDuplicatedEntries(entries, file);
203+
for (var entry : entries) {
204+
containsErrors |= checkTypeReachable(entry, file);
205+
}
206+
207+
return containsErrors;
208+
}
209+
210+
private boolean jniConfigFilesContainsErrors(File file) {
211+
List<Map<String, Object>> entries = getConfigEntries(file);
212+
213+
if (entries.size() == 0) {
214+
System.out.println("ERROR: empty jni-config detected: " + file.toURI());
215+
return true;
216+
}
217+
218+
return containsDuplicatedEntries(entries, file);
219+
}
220+
221+
private boolean containsDuplicatedEntries(List<Map<String, Object>> entries, File file) {
222+
Map<Map<String, Object>, Integer> duplicates = new HashMap<>();
223+
entries.forEach(entry -> duplicates.merge(entry, 1, Integer::sum));
224+
225+
// print all entries that appears more than once
226+
boolean containsDuplicates = false;
227+
for (var entry : duplicates.entrySet()) {
228+
if (entry.getValue() > 1) {
229+
String entryName = (String) entry.getKey().get("name");
230+
if (entryName == null) {
231+
entryName = entry.getKey().toString();
232+
}
233+
containsDuplicates = true;
234+
System.out.println("ERROR: In file " + file.toURI() + " there is a duplicated entry " + entryName);
235+
}
236+
}
237+
238+
return containsDuplicates;
239+
}
240+
241+
@SuppressWarnings("unchecked")
242+
private boolean checkTypeReachable(Map<String, Object> entry, File file) {
243+
// check if condition entry exists
244+
Map<String, Object> condition = (Map<String, Object>) entry.get("condition");
245+
if (condition == null) {
246+
System.out.println("ERROR: In file " + file.toURI() + " there is an entry " + entry + " with missing condition field.");
247+
return true;
248+
}
249+
250+
// check if typeReachable exists inside condition entry
251+
String typeReachable = (String) condition.get("typeReachable");
252+
if (ILLEGAL_TYPE_VALUES.stream().anyMatch(type -> type.equalsIgnoreCase(typeReachable))) {
253+
// get name of entry that misses typeReachable. If name cannot be determinate, write whole entry
254+
String entryName = (String) entry.get("name");
255+
if (entryName == null) {
256+
entryName = entry.toString();
257+
}
258+
259+
System.out.println("ERROR: In file " + file.toURI() + " entry: " + entryName + " contains illegal typeReachable value. Field" +
260+
" typeReachable cannot be any of the following values: " + ILLEGAL_TYPE_VALUES);
261+
return true;
262+
}
263+
264+
return false;
265+
}
266+
267+
private boolean listNullOrEmpty(List list) {
268+
return list == null || list.size() == 0;
269+
}
270+
271+
}
Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package org.graalvm.internal.tck;
22

3-
public class CoordinateUtils {
4-
3+
public abstract class CoordinateUtils {
54
public static String replace(String template, Coordinates coordinates) {
65
return template
76
.replace("$group$", coordinates.group())
@@ -12,4 +11,4 @@ public static String replace(String template, Coordinates coordinates) {
1211
.replace("$version$", coordinates.version());
1312
}
1413

15-
}
14+
}

0 commit comments

Comments
 (0)