source: josm/trunk/test/unit/org/openstreetmap/josm/TestUtils.java@ 17360

Last change on this file since 17360 was 17332, checked in by Don-vip, 3 years ago

fix #20131 - fix unit tests and codestyle violations

  • Property svn:eol-style set to native
File size: 23.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm;
3
4import static org.junit.jupiter.api.Assertions.assertArrayEquals;
5import static org.junit.jupiter.api.Assertions.assertEquals;
6import static org.junit.jupiter.api.Assertions.assertTrue;
7import static org.junit.jupiter.api.Assertions.fail;
8import static org.junit.jupiter.api.Assumptions.assumeFalse;
9
10import java.awt.Component;
11import java.awt.Container;
12import java.awt.Graphics2D;
13import java.io.File;
14import java.io.FileInputStream;
15import java.io.IOException;
16import java.io.InputStream;
17import java.lang.reflect.Field;
18import java.lang.reflect.Method;
19import java.security.AccessController;
20import java.security.PrivilegedAction;
21import java.time.Instant;
22import java.time.ZoneOffset;
23import java.time.format.DateTimeFormatter;
24import java.time.temporal.Temporal;
25import java.util.Arrays;
26import java.util.Collection;
27import java.util.Comparator;
28import java.util.List;
29import java.util.Objects;
30import java.util.Set;
31import java.util.concurrent.ExecutionException;
32import java.util.concurrent.ThreadPoolExecutor;
33import java.util.function.Function;
34import java.util.stream.Collectors;
35import java.util.stream.Stream;
36
37import org.junit.jupiter.api.Assertions;
38import org.openstreetmap.josm.command.Command;
39import org.openstreetmap.josm.data.osm.DataSet;
40import org.openstreetmap.josm.data.osm.Node;
41import org.openstreetmap.josm.data.osm.OsmPrimitive;
42import org.openstreetmap.josm.data.osm.OsmUtils;
43import org.openstreetmap.josm.data.osm.Relation;
44import org.openstreetmap.josm.data.osm.RelationMember;
45import org.openstreetmap.josm.data.osm.Way;
46import org.openstreetmap.josm.gui.MainApplication;
47import org.openstreetmap.josm.gui.progress.AbstractProgressMonitor;
48import org.openstreetmap.josm.gui.progress.CancelHandler;
49import org.openstreetmap.josm.gui.progress.ProgressMonitor;
50import org.openstreetmap.josm.gui.progress.ProgressTaskId;
51import org.openstreetmap.josm.gui.util.GuiHelper;
52import org.openstreetmap.josm.io.Compression;
53import org.openstreetmap.josm.testutils.FakeGraphics;
54import org.openstreetmap.josm.testutils.mockers.JOptionPaneSimpleMocker;
55import org.openstreetmap.josm.testutils.mockers.WindowMocker;
56import org.openstreetmap.josm.tools.JosmRuntimeException;
57import org.openstreetmap.josm.tools.ReflectionUtils;
58import org.openstreetmap.josm.tools.Utils;
59import org.openstreetmap.josm.tools.WikiReader;
60
61import com.github.tomakehurst.wiremock.WireMockServer;
62import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
63
64import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
65import io.github.classgraph.ClassGraph;
66import io.github.classgraph.ClassInfoList;
67import io.github.classgraph.ScanResult;
68import mockit.integration.TestRunnerDecorator;
69
70/**
71 * Various utils, useful for unit tests.
72 */
73public final class TestUtils {
74
75 private TestUtils() {
76 // Hide constructor for utility classes
77 }
78
79 /**
80 * Returns the path to test data root directory.
81 * @return path to test data root directory
82 */
83 public static String getTestDataRoot() {
84 String testDataRoot = System.getProperty("josm.test.data");
85 if (testDataRoot == null || testDataRoot.isEmpty()) {
86 testDataRoot = "test/data";
87 System.out.println("System property josm.test.data is not set, using '" + testDataRoot + "'");
88 }
89 return testDataRoot.endsWith("/") ? testDataRoot : testDataRoot + "/";
90 }
91
92 /**
93 * Gets path to test data directory for given ticket id.
94 * @param ticketid Ticket numeric identifier
95 * @return path to test data directory for given ticket id
96 */
97 public static String getRegressionDataDir(int ticketid) {
98 return TestUtils.getTestDataRoot() + "/regress/" + ticketid;
99 }
100
101 /**
102 * Gets path to given file in test data directory for given ticket id.
103 * @param ticketid Ticket numeric identifier
104 * @param filename File name
105 * @return path to given file in test data directory for given ticket id
106 */
107 public static String getRegressionDataFile(int ticketid, String filename) {
108 return getRegressionDataDir(ticketid) + '/' + filename;
109 }
110
111 /**
112 * Gets input stream to given file in test data directory for given ticket id.
113 * @param ticketid Ticket numeric identifier
114 * @param filename File name
115 * @return path to given file in test data directory for given ticket id
116 * @throws IOException if any I/O error occurs
117 */
118 public static InputStream getRegressionDataStream(int ticketid, String filename) throws IOException {
119 return Compression.getUncompressedFileInputStream(new File(getRegressionDataDir(ticketid), filename));
120 }
121
122 /**
123 * Checks that the given Comparator respects its contract on the given table.
124 * @param <T> type of elements
125 * @param comparator The comparator to test
126 * @param array The array sorted for test purpose
127 */
128 @SuppressFBWarnings(value = "RV_NEGATING_RESULT_OF_COMPARETO")
129 public static <T> void checkComparableContract(Comparator<T> comparator, T[] array) {
130 System.out.println("Validating Comparable contract on array of "+array.length+" elements");
131 // Check each compare possibility
132 for (int i = 0; i < array.length; i++) {
133 T r1 = array[i];
134 for (int j = i; j < array.length; j++) {
135 T r2 = array[j];
136 int a = comparator.compare(r1, r2);
137 int b = comparator.compare(r2, r1);
138 if (i == j || a == b) {
139 if (a != 0 || b != 0) {
140 fail(getFailMessage(r1, r2, a, b));
141 }
142 } else {
143 if (a != -b) {
144 fail(getFailMessage(r1, r2, a, b));
145 }
146 }
147 for (int k = j; k < array.length; k++) {
148 T r3 = array[k];
149 int c = comparator.compare(r1, r3);
150 int d = comparator.compare(r2, r3);
151 if (a > 0 && d > 0) {
152 if (c <= 0) {
153 fail(getFailMessage(r1, r2, r3, a, b, c, d));
154 }
155 } else if (a == 0 && d == 0) {
156 if (c != 0) {
157 fail(getFailMessage(r1, r2, r3, a, b, c, d));
158 }
159 } else if (a < 0 && d < 0) {
160 if (c >= 0) {
161 fail(getFailMessage(r1, r2, r3, a, b, c, d));
162 }
163 }
164 }
165 }
166 }
167 // Sort relation array
168 Arrays.sort(array, comparator);
169 }
170
171 private static <T> String getFailMessage(T o1, T o2, int a, int b) {
172 return new StringBuilder("Compared\no1: ").append(o1).append("\no2: ")
173 .append(o2).append("\ngave: ").append(a).append("/").append(b)
174 .toString();
175 }
176
177 private static <T> String getFailMessage(T o1, T o2, T o3, int a, int b, int c, int d) {
178 return new StringBuilder(getFailMessage(o1, o2, a, b))
179 .append("\nCompared\no1: ").append(o1).append("\no3: ").append(o3).append("\ngave: ").append(c)
180 .append("\nCompared\no2: ").append(o2).append("\no3: ").append(o3).append("\ngave: ").append(d)
181 .toString();
182 }
183
184 /**
185 * Returns a private field value.
186 * @param obj object
187 * @param fieldName private field name
188 * @return private field value
189 * @throws ReflectiveOperationException if a reflection operation error occurs
190 */
191 public static Object getPrivateField(Object obj, String fieldName) throws ReflectiveOperationException {
192 return getPrivateField(obj.getClass(), obj, fieldName);
193 }
194
195 /**
196 * Returns a private field value.
197 * @param cls object class
198 * @param obj object
199 * @param fieldName private field name
200 * @return private field value
201 * @throws ReflectiveOperationException if a reflection operation error occurs
202 */
203 public static Object getPrivateField(Class<?> cls, Object obj, String fieldName) throws ReflectiveOperationException {
204 Field f = cls.getDeclaredField(fieldName);
205 AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
206 f.setAccessible(true);
207 return null;
208 });
209 return f.get(obj);
210 }
211
212 /**
213 * Sets a private field value.
214 * @param obj object
215 * @param fieldName private field name
216 * @param value replacement value
217 * @throws ReflectiveOperationException if a reflection operation error occurs
218 */
219 public static void setPrivateField(
220 final Object obj,
221 final String fieldName,
222 final Object value
223 ) throws ReflectiveOperationException {
224 setPrivateField(obj.getClass(), obj, fieldName, value);
225 }
226
227 /**
228 * Sets a private field value.
229 * @param cls object class
230 * @param obj object
231 * @param fieldName private field name
232 * @param value replacement value
233 * @throws ReflectiveOperationException if a reflection operation error occurs
234 */
235 public static void setPrivateField(
236 final Class<?> cls,
237 final Object obj,
238 final String fieldName,
239 final Object value
240 ) throws ReflectiveOperationException {
241 Field f = cls.getDeclaredField(fieldName);
242 AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
243 f.setAccessible(true);
244 return null;
245 });
246 f.set(obj, value);
247 }
248
249 /**
250 * Returns a private static field value.
251 * @param cls object class
252 * @param fieldName private field name
253 * @return private field value
254 * @throws ReflectiveOperationException if a reflection operation error occurs
255 */
256 public static Object getPrivateStaticField(Class<?> cls, String fieldName) throws ReflectiveOperationException {
257 Field f = cls.getDeclaredField(fieldName);
258 AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
259 f.setAccessible(true);
260 return null;
261 });
262 return f.get(null);
263 }
264
265 /**
266 * Sets a private static field value.
267 * @param cls object class
268 * @param fieldName private field name
269 * @param value replacement value
270 * @throws ReflectiveOperationException if a reflection operation error occurs
271 */
272 public static void setPrivateStaticField(Class<?> cls, String fieldName, final Object value) throws ReflectiveOperationException {
273 Field f = cls.getDeclaredField(fieldName);
274 AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
275 f.setAccessible(true);
276 return null;
277 });
278 f.set(null, value);
279 }
280
281 /**
282 * Returns an instance of {@link AbstractProgressMonitor} which keeps track of the monitor state,
283 * but does not show the progress.
284 * @return a progress monitor
285 */
286 public static ProgressMonitor newTestProgressMonitor() {
287 return new AbstractProgressMonitor(new CancelHandler()) {
288
289 @Override
290 protected void doBeginTask() {
291 }
292
293 @Override
294 protected void doFinishTask() {
295 }
296
297 @Override
298 protected void doSetIntermediate(boolean value) {
299 }
300
301 @Override
302 protected void doSetTitle(String title) {
303 }
304
305 @Override
306 protected void doSetCustomText(String title) {
307 }
308
309 @Override
310 protected void updateProgress(double value) {
311 }
312
313 @Override
314 public void setProgressTaskId(ProgressTaskId taskId) {
315 }
316
317 @Override
318 public ProgressTaskId getProgressTaskId() {
319 return null;
320 }
321
322 @Override
323 public Component getWindowParent() {
324 return null;
325 }
326 };
327 }
328
329 /**
330 * Returns an instance of {@link Graphics2D}.
331 * @return a mockup graphics instance
332 */
333 public static Graphics2D newGraphics() {
334 return new FakeGraphics();
335 }
336
337 /**
338 * Makes sure the given primitive belongs to a data set.
339 * @param <T> OSM primitive type
340 * @param osm OSM primitive
341 * @return OSM primitive, attached to a new {@code DataSet}
342 */
343 public static <T extends OsmPrimitive> T addFakeDataSet(T osm) {
344 new DataSet(osm);
345 return osm;
346 }
347
348 /**
349 * Creates a new node with the given tags (see {@link OsmUtils#createPrimitive(java.lang.String)})
350 *
351 * @param tags the tags to set
352 * @return a new node
353 */
354 public static Node newNode(String tags) {
355 return (Node) OsmUtils.createPrimitive("node " + tags);
356 }
357
358 /**
359 * Creates a new way with the given tags (see {@link OsmUtils#createPrimitive(java.lang.String)}) and the nodes added
360 *
361 * @param tags the tags to set
362 * @param nodes the nodes to add
363 * @return a new way
364 */
365 public static Way newWay(String tags, Node... nodes) {
366 final Way way = (Way) OsmUtils.createPrimitive("way " + tags);
367 for (Node node : nodes) {
368 way.addNode(node);
369 }
370 return way;
371 }
372
373 /**
374 * Creates a new relation with the given tags (see {@link OsmUtils#createPrimitive(java.lang.String)}) and the members added
375 *
376 * @param tags the tags to set
377 * @param members the members to add
378 * @return a new relation
379 */
380 public static Relation newRelation(String tags, RelationMember... members) {
381 final Relation relation = (Relation) OsmUtils.createPrimitive("relation " + tags);
382 for (RelationMember member : members) {
383 relation.addMember(member);
384 }
385 return relation;
386 }
387
388 /**
389 * Creates a new empty command.
390 * @param ds data set
391 * @return a new empty command
392 */
393 public static Command newCommand(DataSet ds) {
394 return new Command(ds) {
395 @Override
396 public String getDescriptionText() {
397 return "";
398 }
399
400 @Override
401 public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted,
402 Collection<OsmPrimitive> added) {
403 // Do nothing
404 }
405 };
406 }
407
408 /**
409 * Ensures 100% code coverage for enums.
410 * @param enumClass enum class to cover
411 */
412 public static void superficialEnumCodeCoverage(Class<? extends Enum<?>> enumClass) {
413 try {
414 Method values = enumClass.getMethod("values");
415 Method valueOf = enumClass.getMethod("valueOf", String.class);
416 ReflectionUtils.setObjectsAccessible(values, valueOf);
417 for (Object o : (Object[]) values.invoke(null)) {
418 assertEquals(o, valueOf.invoke(null, ((Enum<?>) o).name()));
419 }
420 } catch (IllegalArgumentException | ReflectiveOperationException | SecurityException e) {
421 throw new JosmRuntimeException(e);
422 }
423 }
424
425 /**
426 * Get a descendant component by name.
427 * @param root The root component to start searching from.
428 * @param name The component name
429 * @return The component with that name or null if it does not exist.
430 * @since 12045
431 */
432 public static Component getComponentByName(Component root, String name) {
433 if (name.equals(root.getName())) {
434 return root;
435 } else if (root instanceof Container) {
436 Container container = (Container) root;
437 return Stream.of(container.getComponents())
438 .map(child -> getComponentByName(child, name))
439 .filter(Objects::nonNull)
440 .findFirst().orElse(null);
441 } else {
442 return null;
443 }
444 }
445
446 /**
447 * Use to assume that EqualsVerifier is working with the current JVM.
448 */
449 @SuppressWarnings("null")
450 public static void assumeWorkingEqualsVerifier() {
451 if (Utils.getJavaVersion() >= 16) {
452 // Byte Buddy often supports new class file versions for current EA releases if its experimental flag is set to true
453 System.setProperty("net.bytebuddy.experimental", "true");
454 }
455 try {
456 // Workaround to https://github.com/jqno/equalsverifier/issues/177
457 // Inspired by https://issues.apache.org/jira/browse/SOLR-11606
458 nl.jqno.equalsverifier.internal.lib.bytebuddy.ClassFileVersion.ofThisVm();
459 } catch (IllegalArgumentException e) {
460 assumeFalse(e != null);
461 }
462 }
463
464 /**
465 * Use to assume that JMockit is working with the current JVM.
466 */
467 @SuppressWarnings("null")
468 public static void assumeWorkingJMockit() {
469 try {
470 // Workaround to https://github.com/jmockit/jmockit1/issues/534
471 // Inspired by https://issues.apache.org/jira/browse/SOLR-11606
472 new WindowMocker();
473 new JOptionPaneSimpleMocker();
474 } catch (UnsupportedOperationException e) {
475 assumeFalse(e != null);
476 } finally {
477 TestRunnerDecorator.cleanUpAllMocks();
478 }
479 }
480
481 /**
482 * Return WireMock server serving files under ticket directory
483 * @param ticketId Ticket numeric identifier
484 * @return WireMock HTTP server on dynamic port
485 */
486 public static WireMockServer getWireMockServer(int ticketId) {
487 return new WireMockServer(
488 WireMockConfiguration.options()
489 .dynamicPort()
490 .usingFilesUnderDirectory(getRegressionDataDir(ticketId))
491 );
492 }
493
494 /**
495 * Return WireMock server
496 * @return WireMock HTTP server on dynamic port
497 */
498 public static WireMockServer getWireMockServer() {
499 return new WireMockServer(
500 WireMockConfiguration.options()
501 .withRootDirectory("test/data")
502 .dynamicPort()
503 );
504 }
505
506 /**
507 * Renders Temporal to RFC 1123 Date Time
508 * @param time to convert
509 * @return string representation according to RFC1123 of time
510 */
511 public static String getHTTPDate(Temporal time) {
512 return DateTimeFormatter.RFC_1123_DATE_TIME.withZone(ZoneOffset.UTC).format(time);
513 }
514
515 /**
516 * Renders java time stamp to RFC 1123 Date Time
517 * @param time java timestamp (milliseconds from Epoch)
518 * @return string representation according to RFC1123 of time
519 */
520 public static String getHTTPDate(long time) {
521 return getHTTPDate(Instant.ofEpochMilli(time));
522 }
523
524 /**
525 * Throws AssertionError if contents of both files are not equal
526 * @param fileA File A
527 * @param fileB File B
528 */
529 public static void assertFileContentsEqual(final File fileA, final File fileB) {
530 assertTrue(fileA.exists());
531 assertTrue(fileA.canRead());
532 assertTrue(fileB.exists());
533 assertTrue(fileB.canRead());
534 try {
535 try (
536 FileInputStream streamA = new FileInputStream(fileA);
537 FileInputStream streamB = new FileInputStream(fileB);
538 ) {
539 assertArrayEquals(
540 Utils.readBytesFromStream(streamA),
541 Utils.readBytesFromStream(streamB)
542 );
543 }
544 } catch (IOException e) {
545 throw new RuntimeException(e);
546 }
547 }
548
549 /**
550 * Replaces {@linkplain System#lineSeparator() system dependent line separators} with {@code \n}
551 * and calls {@link Assertions#assertEquals(java.lang.Object, java.lang.Object)}.
552 * @param expected expected value
553 * @param actual the value to check against <code>expected</code>
554 */
555 public static void assertEqualsNewline(String expected, String actual) {
556 assertEquals(expected, actual.replace(System.lineSeparator(), "\n"));
557 }
558
559 /**
560 * Waits until any asynchronous operations launched by the test on the EDT or worker threads have
561 * (almost certainly) completed.
562 */
563 public static void syncEDTAndWorkerThreads() {
564 boolean workerQueueEmpty = false;
565 while (!workerQueueEmpty) {
566 try {
567 // once our own task(s) have made it to the front of their respective queue(s),
568 // they're both executing at the same time and we know there aren't any outstanding
569 // worker tasks, then presumably the only way there could be incomplete operations
570 // is if the EDT had launched a deferred task to run on itself or perhaps set up a
571 // swing timer - neither are particularly common patterns in JOSM (?)
572 //
573 // there shouldn't be a risk of creating a deadlock in doing this as there shouldn't
574 // (...couldn't?) be EDT operations waiting on the results of a worker task.
575 workerQueueEmpty = MainApplication.worker.submit(
576 () -> GuiHelper.runInEDTAndWaitAndReturn(
577 () -> ((ThreadPoolExecutor) MainApplication.worker).getQueue().isEmpty()
578 )
579 ).get();
580 } catch (InterruptedException | ExecutionException e) {
581 // inconclusive - retry...
582 workerQueueEmpty = false;
583 }
584 }
585 }
586
587 /**
588 * Returns all JOSM subtypes of the given class.
589 * @param <T> class
590 * @param superClass class
591 * @return all JOSM subtypes of the given class
592 */
593 public static <T> Set<Class<? extends T>> getJosmSubtypes(Class<T> superClass) {
594 try (ScanResult scan = new ClassGraph().whitelistPackages("org.openstreetmap.josm").ignoreClassVisibility().scan()) {
595 Function<String, ClassInfoList> lambda = superClass.isInterface() ? scan::getClassesImplementing : scan::getSubclasses;
596 return lambda.apply(superClass.getName())
597 .asMap().values().stream().map(x -> x.loadClass(superClass)).collect(Collectors.toSet());
598 }
599 }
600
601 /**
602 * Determines if OSM DEV_API credential have been provided. Required for functional tests.
603 * @return {@code true} if {@code osm.username} and {@code osm.password} have been defined on the command line
604 */
605 public static boolean areCredentialsProvided() {
606 return Utils.getSystemProperty("osm.username") != null && Utils.getSystemProperty("osm.password") != null;
607 }
608
609 /**
610 * Returns the ignored error messages listed on
611 * <a href="https://josm.openstreetmap.de/wiki/IntegrationTestIgnores">JOSM wiki</a> for a given test.
612 * @param integrationTest The integration test class
613 * @return the ignored error messages listed on JOSM wiki for this test.
614 * @throws IOException in case of I/O error
615 */
616 public static List<String> getIgnoredErrorMessages(Class<?> integrationTest) throws IOException {
617 return Arrays.stream(new WikiReader()
618 .read("https://josm.openstreetmap.de/wiki/IntegrationTestIgnores?format=txt").split("\\n", -1))
619 .filter(s -> s.startsWith("|| " + integrationTest.getSimpleName() + " ||"))
620 .map(s -> s.substring(s.indexOf("{{{") + 3, s.indexOf("}}}")))
621 .collect(Collectors.toList());
622 }
623}
Note: See TracBrowser for help on using the repository browser.