aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/generator/src/testFixtures/java/tools/refinery/generator/tests/DynamicTestLoader.java
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/generator/src/testFixtures/java/tools/refinery/generator/tests/DynamicTestLoader.java')
-rw-r--r--subprojects/generator/src/testFixtures/java/tools/refinery/generator/tests/DynamicTestLoader.java263
1 files changed, 263 insertions, 0 deletions
diff --git a/subprojects/generator/src/testFixtures/java/tools/refinery/generator/tests/DynamicTestLoader.java b/subprojects/generator/src/testFixtures/java/tools/refinery/generator/tests/DynamicTestLoader.java
new file mode 100644
index 00000000..001ee3a4
--- /dev/null
+++ b/subprojects/generator/src/testFixtures/java/tools/refinery/generator/tests/DynamicTestLoader.java
@@ -0,0 +1,263 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.generator.tests;
7
8import com.google.inject.Inject;
9import com.google.inject.Provider;
10import org.junit.jupiter.api.DynamicNode;
11import org.junit.jupiter.api.function.Executable;
12import org.opentest4j.TestAbortedException;
13import tools.refinery.generator.ModelSemanticsFactory;
14
15import java.io.IOException;
16import java.net.URI;
17import java.net.URISyntaxException;
18import java.net.URL;
19import java.nio.file.FileSystemNotFoundException;
20import java.nio.file.FileSystems;
21import java.nio.file.Files;
22import java.nio.file.Path;
23import java.util.ArrayList;
24import java.util.Enumeration;
25import java.util.List;
26import java.util.stream.Stream;
27
28import static org.junit.jupiter.api.DynamicContainer.dynamicContainer;
29import static org.junit.jupiter.api.DynamicTest.dynamicTest;
30
31public class DynamicTestLoader {
32 private static final String TEST_SUFFIX = ".problem";
33
34 @Inject
35 private Provider<SemanticsTestLoader> testLoaderProvider;
36
37 private Provider<ModelSemanticsFactory> semanticsFactoryProvider;
38
39 @Inject
40 public void setSemanticsFactoryProvider(Provider<ModelSemanticsFactory> semanticsFactoryProvider) {
41 this.semanticsFactoryProvider = semanticsFactoryProvider;
42 }
43
44 public Stream<DynamicNode> allFromClasspath(Class<?> contextClass) {
45 var paths = getExtraPaths(contextClass);
46 if (paths.isEmpty()) {
47 throw new IllegalArgumentException("No file system paths found for class: " + contextClass);
48 }
49 var loader = getTestLoader(paths);
50 var nodes = new ArrayList<DynamicNode>();
51 for (var path : paths) {
52 List<DynamicNode> childNodes;
53 try {
54 childNodes = nodesFromPath(loader, path);
55 } catch (IOException e) {
56 throw new IllegalArgumentException("Failed to enumerate path: " + path, e);
57 }
58 nodes.addAll(childNodes);
59 }
60 if (nodes.isEmpty()) {
61 throw new TestAbortedException("No tests found for class: " + contextClass);
62 }
63 return nodes.stream();
64 }
65
66 private List<DynamicNode> nodesFromPath(SemanticsTestLoader loader, Path path) throws IOException {
67 var nodes = new ArrayList<DynamicNode>();
68 try (var childList = Files.list(path)) {
69 var iterator = childList.iterator();
70 while (iterator.hasNext()) {
71 var childPath = iterator.next();
72 var fileName = childPath.getFileName().toString();
73 var uri = childPath.toUri();
74 try {
75 if (Files.isDirectory(childPath)) {
76 var childNodes = nodesFromPath(loader, childPath);
77 if (!childNodes.isEmpty()) {
78 nodes.add(dynamicContainer(fileName, uri, childNodes.stream()));
79 }
80 } else if (fileName.endsWith(TEST_SUFFIX)) {
81 SemanticsTest test = loader.loadFile(childPath.toFile());
82 if (test != null) {
83 nodes.add(createDynamicNode(fileName, uri, test));
84 }
85 }
86 } catch (IOException | RuntimeException e) {
87 nodes.add(createErrorNode(fileName, uri, e));
88 }
89 }
90 }
91 return nodes;
92 }
93
94 public Stream<DynamicNode> fromClasspath(Class<?> contextClass, String name) {
95 var loader = getTestLoader(contextClass);
96 URL url;
97 URI uri;
98 try {
99 url = safeGetResource(contextClass, name);
100 uri = url.toURI();
101 } catch (RuntimeException | URISyntaxException e) {
102 return Stream.of(createErrorNode(name, null, e));
103 }
104 SemanticsTest test;
105 try {
106 test = loadFromUrl(loader, uri, url);
107 } catch (IOException | RuntimeException e) {
108 return Stream.of(createErrorNode(name, uri, e));
109 }
110 return createDynamicNodes(uri, test);
111 }
112
113 public Stream<DynamicNode> fromClasspath(Class<?> contextClass, String... names) {
114 return fromClasspath(contextClass, Stream.of(names));
115 }
116
117 public Stream<DynamicNode> fromClasspath(Class<?> contextClass, Stream<String> names) {
118 var loader = getTestLoader(contextClass);
119 return names.map(name -> nodeFromClasspath(loader, contextClass, name));
120 }
121
122 private DynamicNode nodeFromClasspath(SemanticsTestLoader loader, Class<?> contextClass, String name) {
123 URL url;
124 URI uri;
125 try {
126 url = safeGetResource(contextClass, name);
127 uri = url.toURI();
128 } catch (RuntimeException | URISyntaxException e) {
129 return createErrorNode(name, null, e);
130 }
131 SemanticsTest test;
132 try {
133 test = loadFromUrl(loader, uri, url);
134 } catch (IOException | RuntimeException e) {
135 return createErrorNode(name, uri, e);
136 }
137 return createDynamicNode(name, uri, test);
138 }
139
140 private static URL safeGetResource(Class<?> contextClass, String name) {
141 var url = contextClass.getResource(name);
142 if (url == null) {
143 throw new IllegalStateException("Test resource %s was not found.".formatted(name));
144 }
145 return url;
146 }
147
148 private SemanticsTest loadFromUrl(SemanticsTestLoader testLoader, URI uri, URL url) throws IOException {
149 var eclipseUri = org.eclipse.emf.common.util.URI.createURI(uri.toString());
150 try (var inputStream = url.openStream()) {
151 return testLoader.loadStream(inputStream, eclipseUri);
152 }
153 }
154
155 public Stream<DynamicNode> fromString(Class<?> contextClass, String problem) {
156 var testLoader = getTestLoader(contextClass);
157 SemanticsTest test;
158 try {
159 test = testLoader.loadString(problem);
160 } catch (RuntimeException e) {
161 return Stream.of(createErrorNode("<string>", null, e));
162 }
163 return createDynamicNodes(null, test);
164 }
165
166 public Stream<DynamicNode> fromString(String problem) {
167 return fromString(null, problem);
168 }
169
170 private DynamicNode createDynamicNode(String name, URI uri, SemanticsTest test) {
171 var testCases = test.testCases();
172 if (testCases.size() == 1 && testCases.getFirst().name() == null) {
173 var testCase = testCases.getFirst();
174 return dynamicTest(name, uri, asExecutable(testCase));
175 }
176 var children = createDynamicNodes(uri, test);
177 return dynamicContainer(name, uri, children);
178 }
179
180 private Stream<DynamicNode> createDynamicNodes(URI uri, SemanticsTest test) {
181 var testCases = test.testCases();
182 var children = new ArrayList<DynamicNode>();
183 int testCaseCount = testCases.size();
184 for (int i = 0; i < testCaseCount; i++) {
185 var testCase = testCases.get(i);
186 var testCaseName = testCase.name();
187 if (testCaseName == null) {
188 testCaseName = "[%d]".formatted(i + 1);
189 }
190 children.add(dynamicTest(testCaseName, uri, asExecutable(testCase)));
191 }
192 return children.stream();
193 }
194
195 private Executable asExecutable(SemanticsTestCase testCase) {
196 return () -> testCase.execute(semanticsFactoryProvider.get());
197 }
198
199 private DynamicNode createErrorNode(String name, URI uri, Throwable cause) {
200 var messageBuilder = new StringBuilder();
201 messageBuilder.append("Error while reading test '").append(name).append("'");
202 if (uri != null) {
203 messageBuilder.append(" from ").append(uri);
204 }
205 if (cause != null) {
206 messageBuilder.append(": ").append(cause.getMessage());
207 }
208 var message = messageBuilder.toString();
209 return dynamicTest(name, uri, () -> {
210 throw new RuntimeException(message, cause);
211 });
212 }
213
214 private SemanticsTestLoader getTestLoader(Class<?> contextClass) {
215 var extraPaths = getExtraPaths(contextClass);
216 return getTestLoader(extraPaths);
217 }
218
219 private SemanticsTestLoader getTestLoader(List<Path> extraPaths) {
220 var loader = testLoaderProvider.get();
221 extraPaths.forEach(loader::extraPath);
222 return loader;
223 }
224
225 private List<Path> getExtraPaths(Class<?> contextClass) {
226 if (contextClass == null) {
227 return List.of();
228 }
229 var resourceName = contextClass.getPackageName().replace('.', '/');
230 Enumeration<URL> resources;
231 try {
232 resources = contextClass.getClassLoader().getResources(resourceName);
233 } catch (IOException e) {
234 // Failed to find any resources.
235 return List.of();
236 }
237 var directories = new ArrayList<Path>();
238 while (resources.hasMoreElements()) {
239 var url = resources.nextElement();
240 var path = getPath(url);
241 if (path != null && path.getFileSystem() == FileSystems.getDefault()) {
242 directories.add(path);
243 }
244 }
245 return directories;
246 }
247
248 private static Path getPath(URL url) {
249 URI uri;
250 try {
251 uri = url.toURI();
252 } catch (URISyntaxException e) {
253 return null;
254 }
255 Path path;
256 try {
257 path = Path.of(uri);
258 } catch (FileSystemNotFoundException e) {
259 return null;
260 }
261 return path.toAbsolutePath();
262 }
263}